2009-11-24 4 views
3

Mon application Python/SQLAlchemy gère un ensemble de noeuds, tous dérivés d'une classe de base Noeud. J'utilise les fonctionnalités de polymorphisme de SQLAlchemy pour gérer les noeuds dans une table SQLite3. Voici la définition de la base classe Node:Création d'une relation de conteneur dans SQLAlchemy déclaratif

class Node(db.Base): 
    __tablename__ = 'nodes' 
    id = Column(Integer, primary_key=True) 
    node_type = Column(String(40)) 
    title = Column(UnicodeText) 
    __mapper_args__ = {'polymorphic_on': node_type} 

et, à titre d'exemple, l'une des classes dérivées, NoteNode:

class NoteNode(Node): 
    __mapper_args__ = {'polymorphic_identity': 'note'} 
    __tablename__ = 'nodes_note' 
    id = Column(None,ForeignKey('nodes.id'),primary_key=True) 
    content_type = Column(String) 
    content = Column(UnicodeText) 

Maintenant, je besoin d'un nouveau type de noeud, ListNode, c'est-à-dire un conteneur ordonné de zéro ou plus Noeud s. Quand je charge un ListNode, je veux qu'il ait son ID et son titre (à partir de la classe de base) avec une collection de ses nœuds contenus (enfants). Un Le noeud peut apparaître dans plus d'un ListNode, donc ce n'est pas une hiérarchie correcte. Je les crée dans ce sens:

note1 = NoteNode(title=u"Note 1", content_type="text/text", content=u"I am note #1") 
session.add(note1) 

note2 = NoteNode(title=u"Note 2", content_type="text/text", content=u"I am note #2") 
session.add(note2) 

list1 = ListNode(title=u"My List") 
list1.items = [note1,note2] 
session.add(list1) 

La liste des enfants doit seulement consister en noeud objets - qui est, tout ce que je besoin est leur truc de classe de base. Ils ne devraient pas être entièrement réalisés dans les classes spécialisées (donc je ne reçois pas le graphique entier à la fois, entre autres raisons).

j'ai commencé le long des lignes suivantes, bricoler des morceaux que je trouve dans divers endroits sans une compréhension complète de ce qui se passait, donc cela ne peut pas faire beaucoup de sens:

class ListNode(Node): 
    __mapper_args__ = {'polymorphic_identity': 'list', 'inherit_condition':id==Node.id} 
    __tablename__ = 'nodes_list_contents' 
    id = Column(None, ForeignKey('nodes.id'), primary_key=True) 
    item_id = Column(None, ForeignKey('nodes.id'), primary_key=True) 
    items = relation(Node, primaryjoin="Node.id==ListNode.item_id") 

Cette approche échoue de plusieurs façons: il ne semble pas autoriser un ListNode vide, et la définition de l'attribut items à une liste entraîne dans SQLAlchemy se plaignant que l'objet 'list' n'a pas d'attribut '_sa_instance_state'. Pas étonnant, des heures de mutations aléatoires sur ce thème n'ont donné aucun de bons résultats,

J'ai une expérience limitée dans SQLAlchemy mais je veux vraiment que cela fonctionne bientôt. J'apprécierais beaucoup tout conseil ou direction que vous pouvez offrir. Merci d'avance!

Répondre

5

Vous avez besoin d'une table supplémentaire pour plusieurs à-plusieurs:

nodes_list_nodes = Table(
    'nodes_list_nodes', metadata, 
    Column('parent_id', None, ForeignKey('nodes_list.id'), nullable=False), 
    Column('child_id', None, ForeignKey(Node.id), nullable=False), 
    PrimaryKeyConstraint('parent_id', 'child_id'), 
) 

class ListNode(Node): 
    __mapper_args__ = {'polymorphic_identity': 'list'} 
    __tablename__ = 'nodes_list' 
    id = Column(None, ForeignKey('nodes.id'), primary_key=True) 
    items = relation(Node, secondary=nodes_list_nodes) 

Mise à jour: ci-dessous un exemple pour la liste ordonnée en utilisant association_proxy:

from sqlalchemy.orm.collections import InstrumentedList 
from sqlalchemy.ext.associationproxy import association_proxy 


class ListNodeAssociation(Base): 
    __tablename__ = 'nodes_list_nodes' 
    parent_id = Column(None, ForeignKey('nodes_list.id'), primary_key=True) 
    child_id = Column(None, ForeignKey(Node.id), primary_key=True) 
    order = Column(Integer, nullable=False, default=0) 
    child = relation(Node) 
    __table_args__ = (
     PrimaryKeyConstraint('parent_id', 'child_id'), 
     {}, 
    ) 


class OrderedList(InstrumentedList): 

    def append(self, item): 
     if self: 
      item.order = self[-1].order+1 
     else: 
      item.order = 1 
     InstrumentedList.append(self, item) 


class ListNode(Node): 
    __mapper_args__ = {'polymorphic_identity': 'list'} 
    __tablename__ = 'nodes_list' 
    id = Column(None, ForeignKey('nodes.id'), primary_key=True) 
    _items = relation(ListNodeAssociation, 
         order_by=ListNodeAssociation.order, 
         collection_class=OrderedList, 
         cascade='all, delete-orphan') 
    items = association_proxy(
       '_items', 'child', 
       creator=lambda item: ListNodeAssociation(child=item)) 
+0

Et cela se traduira par " items "étant une liste d'objets Item réels, correctement? La relation binaire finit par être complètement transparente de sorte qu'il n'a pas besoin d'une associationproxy pour passer devant les objets réels, n'est-ce pas? –

+0

Bon, cette affaire est facile. associationproxy est nécessaire lorsque vous utilisez un modèle intermédiaire, c'est-à-dire pour rendre la liste ordonnée (champ d'ordre d'addition dans la table de connexion). –

+0

Merci beaucoup, Denis - cela a vraiment brisé la glace.Si je peux le poursuivre un peu plus loin, j'ai réécrit la table de relations dans le style déclaratif: class ListNodeAssociation (Base): __nablename__ = 'nodes_list_nodes' parent_id = Colonne (None, ForeignKey ('nodes_list.id'), primary_key = True) child_id = Colonne (None, ForeignKey (Node.id), primary_key = True) J'ai toujours besoin que la relation soit en ordre, idéalement via le mécanisme 'orderinglist' de SQLAlchemy, mais je ne vois pas comment s'applique dans cette situation ... est-ce applicable, et si oui, comment? – jgarbers

Questions connexes