【问题标题】:Creating self-referential tables with polymorphism in SQLALchemy在 SQLALchemy 中创建具有多态性的自引用表
【发布时间】:2014-04-15 18:03:48
【问题描述】:

我正在尝试创建一个数据库结构,其中包含多种类型的内容实体,其中一种是评论,可以附加到任何其他类型。

考虑以下几点:

from datetime import datetime
from sqlalchemy import create_engine
from sqlalchemy import Column, ForeignKey
from sqlalchemy import Unicode, Integer, DateTime
from sqlalchemy.orm import relation, backref
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Entity(Base):
    __tablename__ = 'entities'
    id = Column(Integer, primary_key=True)
    created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
    edited_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
    type = Column(Unicode(20), nullable=False)
    __mapper_args__ = {'polymorphic_on': type}

# <...insert some models based on Entity...>

class Comment(Entity):
    __tablename__ = 'comments'
    __mapper_args__ = {'polymorphic_identity': u'comment'}
    id = Column(None, ForeignKey('entities.id'), primary_key=True)
    _idref = relation(Entity, foreign_keys=id, primaryjoin=id == Entity.id)
    attached_to_id = Column(Integer, ForeignKey('entities.id'), nullable=False)
    #attached_to = relation(Entity, remote_side=[Entity.id])
    attached_to = relation(Entity, foreign_keys=attached_to_id,
                           primaryjoin=attached_to_id == Entity.id,
                           backref=backref('comments', cascade="all, delete-orphan"))

    text = Column(Unicode(255), nullable=False)

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

这似乎是正确的,除了 SQLAlchemy 不喜欢有两个外键指向同一个父级。它说ArgumentError: Can't determine join between 'entities' and 'comments'; tables have more than one foreign key constraint relationship between them. Please specify the 'onclause' of this join explicitly.

如何指定onclause

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    尝试将您的Comment.__mapper_args__ 补充为:

    __mapper_args__ = {
        'polymorphic_identity': 'comment',
        'inherit_condition': (id == Entity.id),
    }
    

    附:我不明白你需要_idref 关系做什么?如果您想在需要 Entity 的地方使用注释,您可以按原样传递 Comment 实例。

    【讨论】:

    • _idref 试图向 SQLAlchemy 阐明哪个关系指的是什么。它在我的代码中没有用。
    • 所以我刚刚尝试了这个,它解决了这个问题,但引入了一个新的:删除任何其他实体(具有此模型的反向引用,但没有实例)失败,TypeError: id() takes exactly one argument (0 given)
    • 没有完全理解您的评论,但看起来您对内置 Python id() 函数感到困惑。您是否忘记在 id 前面加上self.?或者您是否在id 字段的声明之后 声明类声明中移动了__mapper_args__
    • 啊!就是这样。 __mapper_args__之前 id。应该是后。它现在正在工作。
    • 这个答案对我也有帮助......我知道这有点晚了,但有什么理由可以这样做吗?也许到文档的链接会很有启发性。提前致谢。
    【解决方案2】:

    补充@nailxx 的回答: 如果您有许多从属表,那么重复所有样板文件是乏味的。 解决方案:将其全部移至元类。

    class EntityMeta(type(Entity)):
        def __init__(cls, name, bases, dct):
            ident = dct.get('_identity',None)
            if '__abstract__' not in dct:
                xid = Column(None, ForeignKey(Entity.id), primary_key=True)
                setattr(cls,'id',xid)
                setattr(cls,'__mapper_args__', { 'polymorphic_identity': dct['_identity'], 'inherit_condition': (xid == Entity.id,), 'primary_key':(xid,) })
                setattr(cls,'__tablename__',name.lower())
            super(EntityMeta, cls).__init__(name, bases, dct)
    
    class EntityRef(Entity):
        __abstract__ = True
        __metaclass__ = EntityMeta
    
    class Comment(EntityRef):
        _identity = 'comment'
        [...]
    

    变体,作为读者练习:您可以省略_identity=… 语句并使用类名,如setattr(cls,'__tablename__',…) 行。或者不覆盖现有的 __tablename__ 或 __mapper_args__ 属性。

    一定要测试dct 而不是cls 是否存在:后者会在父类中找到属性,此时您显然不想要这些属性。

    【讨论】:

      猜你喜欢
      • 2011-06-21
      • 1970-01-01
      • 2015-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多