SQLAlchemy中具有多个父项的通用关联

时间:2012-05-22 09:01:35

标签: python sqlalchemy

我试图了解SQLAlchemy附带的“Discriminator on association”示例,它定义了HasAddresses mixin,因此每个模型子类化HasAddresses都会神奇地获得addresses属性,这是一个集合,可以添加地址对象。链接是通过中间表执行的,所以乍一看关系看起来像多对多,我希望能够将多个地址链接到客户,还有多个客户和供应商链接到地址。

然而,Address模型的设置方式使其具有单个parent属性,该属性只能引用单个对象。因此,在示例中,地址只能链接到单个客户或供应商。

如何修改该示例,以便Address能够反向引用多个父对象?

1 个答案:

答案 0 :(得分:5)

我们可以修改sqlalchemy / examples / generic_associations / table_per_association.py,为地址添加一个命名的backref,然后添加一个@property,它汇总所有创建的backref。

"""table_per_association.py

The HasAddresses mixin will provide a new "address_association" table for
each parent class.   The "address" table will be shared
for all parents.

This configuration has the advantage that all Address
rows are in one table, so that the definition of "Address"
can be maintained in one place.   The association table 
contains the foreign key to Address so that Address
has no dependency on the system.


"""
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
import itertools

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 Address(Base):
    """The Address class.   

    This represents all address records in a 
    single table.

    """
    street = Column(String)
    city = Column(String)
    zip = Column(String)

    @property
    def all_owners(self):
        return list(
            itertools.chain(
            *[
                getattr(self, attr)
                for attr in [a for a in dir(self) if a.endswith("_parents")]
            ]
        ))

    def __repr__(self):
        return "%s(street=%r, city=%r, zip=%r)" % \
            (self.__class__.__name__, self.street, 
            self.city, self.zip)

class HasAddresses(object):
    """HasAddresses mixin, creates a new address_association
    table for each parent.

    """
    @declared_attr
    def addresses(cls):
        address_association = Table(
            "%s_addresses" % cls.__tablename__,
            cls.metadata,
            Column("address_id", ForeignKey("address.id"), 
                                primary_key=True),
            Column("%s_id" % cls.__tablename__, 
                                ForeignKey("%s.id" % cls.__tablename__), 
                                primary_key=True),
        )
        return relationship(Address, secondary=address_association, 
                    backref="%s_parents" % cls.__name__.lower())

class Customer(HasAddresses, Base):
    name = Column(String)

class Supplier(HasAddresses, Base):
    company_name = Column(String)

engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)

session = Session(engine)

a1 = Address(
            street='123 anywhere street',
            city="New York",
            zip="10110")
a2 = Address(
            street='40 main street',
            city="San Francisco",
            zip="95732")

session.add_all([
    Customer(
        name='customer 1', 
        addresses=[a1, a2]
    ),
    Supplier(
        company_name="Ace Hammers",
        addresses=[a1]
    ),
])

session.commit()

for customer in session.query(Customer):
    for address in customer.addresses:
        print address.all_owners