2010-05-19 11 views
5

Sto provando a creare una struttura di database in cui sono presenti molti tipi di entità di contenuto, di cui una, un commento, può essere associata a qualsiasi altra.Creazione di tabelle autoreferenziali con polimorfismo in SQLALchemy

Si consideri il seguente:

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) 

Questo sembra circa la destra, ad eccezione SQLAlchemy non piace avere due chiavi esterne che puntano allo stesso genitore. Dice 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.

Come specificare onclause?

risposta

10

Prova a completare il vostro Comment.__mapper_args__ a:

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

P.S. Non ho capito per cosa hai bisogno della relazione _idref? Se si desidera utilizzare un commento da qualche parte in cui è previsto Entity, è possibile passare l'istanza Comment così com'è.

+0

L'_idref è stato un tentativo di chiarire a SQLAlchemy quale relazione si riferisce a cosa. È inutile nel mio codice. –

+0

Quindi ho appena provato questo ed è stato corretto il problema, ma ne ho introdotto uno nuovo: eliminare qualsiasi altra entità (che ha un backref da questo modello, ma nessuna istanza) fallisce con 'TypeError: id() richiede esattamente un argomento (0 dato) ' –

+0

Non ho capito completamente il tuo commento ma sembra che tu abbia problemi con la funzione' id() 'di Python. Hai dimenticato di anteporre id con 'self'? O hai spostato '__mapper_args__' nella dichiarazione di classe * dopo la * dichiarazione del campo' id'? – nkrkv

3

Per completare la risposta @ nailxx: La ripetizione di tutto questo codice è noiosa se ci sono molte tabelle dipendenti. Soluzione: sposta tutto in una metaclasse.

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' 
    [...] 

Variazioni, come esercizio per il lettore: è possibile omettere la dichiarazione _identity=… e utilizzare il nome della classe, come nella linea setattr(cls,'__tablename__',…). O non sovrascrivere un attributo __tablename__ o __mapper_args__ esistente.

Assicurati di testare dct e non cls per l'esistenza: quest'ultimo troverà attributi nella classe genitore, che ovviamente non vuoi a questo punto.

Problemi correlati