SQLAlchemy使用字段连接表以对行

时间:2016-01-19 05:13:35

标签: python sqlalchemy

我们正在从Grails迁移到Python(和Flask)。我希望能够使用SQLAlchemy中Grails应用程序的当前数据库结构。

在Grails中,我有一些像这样的域类:

class Asset {
  static hasMany = [documents:AssetDocument]
  List<AssetDocument> documents = new ArrayList<AssetDocument>();    
...
}

class AssetDocument {
...
}

这将在Postgresql中创建一些像这样的表

CREATE TABLE asset
(
  id bigint NOT NULL,
  version bigint NOT NULL,
... etc
)

CREATE TABLE asset_document
(
  id bigint NOT NULL,
  version bigint NOT NULL,
    .... etc
    )

最后

CREATE TABLE asset_asset_document
(
  asset_documents_id bigint,
  asset_document_id bigint,
  documents_idx integer,
  CONSTRAINT fk_4pqxhfusa9cevslboxbucsjdn FOREIGN KEY (asset_document_id)
      REFERENCES asset_document (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)

asset_asset_document表将Asset表连接到Asset_ 文件表。

在Grails中,因为我将关系声明为List,所以有一个Extra字段document_idx这是数组中的索引,所以对于像这样的表

资产

 +-----+---------+-------+
 | ID  | Version | etc.. |
 +-----+---------+-------+
 |   1 |       0 | ..... |
 |   2 |       0 | ..... |
 +-----+---------+-------+

asset_document

+-----+---------+-------+
| ID  | Version | etc.. |
+-----+---------+-------+
|   1 |       0 | ..... |
|   2 |       0 | ..... |
|   3 |       0 | ..... |
+-----+---------+-------+

asset_asset_document

+---------------------+--------------------+----------------+
| asset_documents_id  | asset_document_id  | documents_idx  |
+---------------------+--------------------+----------------+
|                   1 |                  1 |              0 |
|                  1  |                  2 |              1 |
|                   2 |                  3 |              0 |
+---------------------+--------------------+----------------+

使用此示例资产,其中id = 1有两个asset_documents,其中ids = 1,2 where asset id = 2,有一个asset_document,id = 3

documents_idx字段根据AssetDocuments数组中的索引递增。

如何在SQLAlchemy中映射它,以便如果我将AssetDocument添加到我的资产关系中,document_idx也会更新?

例如,如果我将AssetDocument添加到资产中,其中id = 2

+---------------------+--------------------+----------------+
| asset_documents_id  | asset_document_id  | documents_idx  |
+---------------------+--------------------+----------------+
|                   1 |                  1 |              0 |
|                  1  |                  2 |              1 |
|                   2 |                  3 |              0 |
|                   2 |                  5 |              1 |
+---------------------+--------------------+----------------+

到目前为止,我可以看到我需要

t_asset_asset_document = Table(
    'asset_asset_document', metadata,
    Column('asset_documents_id', BigInteger),
    Column('asset_document_id', ForeignKey(u'asset_document.id')),
    Column('documents_idx', Integer)
)

class Asset(Base):
    __tablename__ = 'asset'

    id = Column(BigInteger, Sequence('seq_asset'), primary_key=True)
    version = Column(BigInteger, nullable=False)
    documents = relationship("AssetDocument",
                    secondary=t_asset_asset_document) #What needs to be here??

class AssetDocument(Base):
    __tablename__ = 'asset_document'

    id = Column(BigInteger, Sequence('seq_asset_document'), primary_key=True)
    version = Column(BigInteger, nullable=False)

1 个答案:

答案 0 :(得分:4)

Ordering List自动处理此类索引更新。但是,它并不适用于多对多关系。为了解决这个问题,可以使用Association Proxy扩展名。

基于您的模型的完整工作代码如下:

from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.orderinglist import ordering_list

# ...

# :> convert relationship table to a mapped object
class AssetAssetDocument(Base):
    __tablename__ = 'asset_asset_document'

    asset_id = Column(ForeignKey(u'asset.id'), primary_key=True)
    asset_document_id = Column(ForeignKey(u'asset_document.id'), primary_key=True)
    documents_idx = Column(Integer)

    # :> define one-way relationship; will be used by association proxy
    document = relationship('AssetDocument')


class Asset(Base):
    __tablename__ = 'asset'

    id = Column(BigInteger, Sequence('seq_asset'), primary_key=True)
    version = Column(BigInteger, nullable=False)

    # :> define a relationship to a mapped relationship object
    # this one is able to use `ordering_list` factory
    _documents = relationship(
        AssetAssetDocument,
        order_by='AssetAssetDocument.documents_idx',
        collection_class=ordering_list('documents_idx'),
    )

    # :> define a desired relationship
    documents = association_proxy(
        '_documents', 'document',
        creator=lambda _d: AssetAssetDocument(document=_d),
    )


class AssetDocument(Base):
    __tablename__ = 'asset_document'

    id = Column(BigInteger, Sequence('seq_asset_document'), primary_key=True)
    version = Column(BigInteger, nullable=False)

用法很简单:

# INSERT TEST DATA
a1 = Asset(
    version=0,
    documents=[
        AssetDocument(version=1),
        AssetDocument(version=2),
    ])
session.add(a1)
session.commit()

# TEST MODIFICATIONS
# remove first one
del a1.documents[0]
# insert a position 0
a1.documents.insert(0, AssetDocument(version=3))
# add at the end
a1.documents.append(AssetDocument(version=4))
session.commit()


# check that the document_idx are properly ordered
q = (session
     .query(AssetAssetDocument.asset_document_id)
     .filter(AssetAssetDocument.asset_id == a1.id)
     .order_by(AssetAssetDocument.documents_idx)
     )
doc_ids = [x.asset_document_id for x in q]
assert doc_ids == [3, 2, 4]