Создание самоссылочных таблиц с полиморфизмом в SQLALchemy - PullRequest
5 голосов
/ 19 мая 2010

Я пытаюсь создать структуру БД, в которой у меня есть много типов сущностей контента, один из которых, Комментарий, может быть присоединен к любому другому.

Обратите внимание на следующее:

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 ]

10 голосов
/ 19 мая 2010

Попробуйте пополнить свой Comment.__mapper_args__ до:

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

P.S. Я не понимаю, для чего вам нужны _idref отношения? Если вы хотите использовать комментарий где-то, где ожидается Entity, вы можете просто передать экземпляр Comment как есть.

3 голосов
/ 15 апреля 2014

В дополнение к ответу @ 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 на наличие: последний найдет атрибуты в родительском классе, которые вам явно не нужны на этом этапе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...