views:

167

answers:

1

I'm trying to create a db structure in which I have many types of content entities, of which one, a Comment, can be attached to any other.

Consider the following:

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)

This seems about right, except SQLAlchemy doesn't like having two foreign keys pointing to the same parent. It says 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.

How do I specify onclause?

+2  A: 

Try to supplement your Comment.__mapper_args__ to:

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

P.S. I haven't understand what you need _idref relationship for? If you want to use a comment somewhere where Entity is expected you could just pass the Comment instance as is.

nailxx
The _idref was an attempt to clarify to SQLAlchemy which relation refers to what. It's of no use in my code.
Jace
So I just tried this and it's fixed the problem, but introduced a new one: deleting any other entity (which has a backref from this model, but no instance) fails with `TypeError: id() takes exactly one argument (0 given)`
Jace
Haven't understand your comment completely but look like you're messed up with built-in Python `id()` function. Did you forget to prepend id with `self.`? Or did you move `__mapper_args__` in class declaration *after* declaration of `id` field?
nailxx
Ah! That was it. `__mapper_args__` was *before* id. Should have been after. It's working now.
Jace