实例化对象中关系为None的SQLAlchemy继承



我希望在继承的(mixin)类中有一个"relationship"。

但是,当我创建继承的对象时,关系对象为None。我无法附加。

如何解决此问题?

以下是基于文档的代码

from sqlalchemy import Column, Integer, String, DateTime, Boolean, BigInteger, Float
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, backref
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Target(Base):
    __tablename__ = "target"
    id = Column(Integer, primary_key=True)
class RefTargetMixin(object):
    @declared_attr
    def target_id(cls):
        return Column('target_id', ForeignKey('target.id'))
    @declared_attr
    def target(cls):
        return relationship("Target",
            primaryjoin="Target.id==%s.target_id" % cls.__name__
        )
class Foo(RefTargetMixin, Base):
    __tablename__ = 'foo'
    id = Column(Integer, primary_key=True)
print repr(RefTargetMixin.target)
print repr(Foo.target)
print repr(Foo().target)

输出为:

<sqlalchemy.orm.properties.RelationshipProperty object at 0x24e7890>
<sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x24e7690>
None

一般来说,我应该能够附加到关系对象(目标),但这里我不能,因为它是None。为什么?

之所以值为None,是因为您将其定义为多对一关系。从父级到子级的多对一意味着父级上有一个外键,该外键只能引用一个且只能引用一个子级。如果您希望RefTargetMixin类的某些内容引用项的集合,那么外键必须位于远程端。

因此,这里的目标是使RefTargetMixin的子类中的任何对象都成为Target的潜在父对象。这种模式被称为多态关联模式。虽然在许多ORM工具包中,通过在Target上声明"多态外键"来提供这一点是很常见的,但这在关系上不是一个好的做法,因此答案是以某种方式使用多个表。在examples/generic_association文件夹中的SQLAlchemy核心中提供了三种场景,包括"带鉴别器的单个关联表"、"每个关联的表"one_answers"每个相关的表"。这里,每个模式都为RefTargetMixin提供了相同的声明性模式,但表的结构发生了变化。

例如,这里是您使用"每个关联的表"的模型,在我看来,如果您不需要同时查询多种类型的RefTargetMixin对象,它往往会扩展到最佳规模(注意,我确实按原样使用了这个示例,只是更改了名称):

from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy import create_engine, Integer, Column, 
                    String, ForeignKey, Table
from sqlalchemy.orm import Session, relationship
class Base(object):
    """Base class which provides automated table name
    and surrogate primary key column.
    """
    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()
    id = Column(Integer, primary_key=True)
Base = declarative_base(cls=Base)
class Target(Base):
    pass
class RefTargetMixin(object):
    @declared_attr
    def targets(cls):
        target_association = Table(
            "%s_targets" % cls.__tablename__,
            cls.metadata,
            Column("target_id", ForeignKey("target.id"),
                                primary_key=True),
            Column("%s_id" % cls.__tablename__,
                                ForeignKey("%s.id" % cls.__tablename__),
                                primary_key=True),
        )
        return relationship(Target, secondary=target_association)
class Customer(RefTargetMixin, Base):
    name = Column(String)
class Supplier(RefTargetMixin, Base):
    company_name = Column(String)
engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
session = Session(engine)
session.add_all([
    Customer(
        name='customer 1',
        targets=[
            Target(),
            Target()
        ]
    ),
    Supplier(
        company_name="Ace Hammers",
        targets=[
            Target(),
        ]
    ),
])
session.commit()
for customer in session.query(Customer):
    for target in customer.targets:
        print target

这是正常行为:Foo有一个Target。创建Foo对象时,它还没有Target,因此Foo().target的值为None

如果您希望Foo有多个Target,那么应该在Target中放置一个foo_id,而不是在Foo中放置target_id,并使用backref。

此外,在这种情况下,不需要指定主联接。

最新更新