在SQLAlchemy中创建具有多态性的自引用表
我正在尝试创建一个数据库结构,这个结构里有很多种内容实体,其中一种是评论(Comment),它可以附加到任何其他实体上。
考虑以下内容:
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
呢?
2 个回答
3
为了补充@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
:后者会查找父类中的属性,这在这个时候显然是不想要的。
11
试着把你的 Comment.__mapper_args__
补充成:
__mapper_args__ = {
'polymorphic_identity': 'comment',
'inherit_condition': (id == Entity.id),
}
顺便说一下,我不太明白你为什么需要 _idref
这种关系?如果你想在需要 Entity
的地方使用评论,你可以直接传递 Comment
实例就可以了。