2012-06-18 1 views
6

Ich habe zwei Tabellen, tablet und correspondent:KeyError beim Hinzufügen von Objekten zu SQLAlchemy Assoziationsobjekt

class Correspondent(db.Model, GlyphMixin): 
    # PK column and tablename etc. come from the mixin 
    name = db.Column(db.String(100), nullable=False, unique=True) 
    # association proxy 
    tablets = association_proxy('correspondent_tablets', 'tablet') 

def __init__(self, name, tablets=None): 
    self.name = name 
    if tablets: 
     self.tablets = tablets 


class Tablet(db.Model, GlyphMixin): 
    # PK column and tablename etc. come from the mixin 
    area = db.Column(db.String(100), nullable=False, unique=True) 
    # association proxy 
    correspondents = association_proxy('tablet_correspondents', 'correspondent') 

def __init__(self, area, correspondents=None): 
    self.area = area 
    if correspondents: 
     self.correspondents = correspondents 


class Tablet_Correspondent(db.Model): 

    __tablename__ = "tablet_correspondent" 
    tablet_id = db.Column("tablet_id", 
     db.Integer(), db.ForeignKey("tablet.id"), primary_key=True) 
    correspondent_id = db.Column("correspondent_id", 
     db.Integer(), db.ForeignKey("correspondent.id"), primary_key=True) 
    # relations 
    tablet = db.relationship(
     "Tablet", 
     backref="tablet_correspondents", 
     cascade="all, delete-orphan", 
     single_parent=True) 
    correspondent = db.relationship(
     "Correspondent", 
     backref="correspondent_tablets", 
     cascade="all, delete-orphan", 
     single_parent=True) 

    def __init__(self, tablet=None, correspondent=None): 
     self.tablet = tablet 
     self.correspondent = correspondent 

Ich kann Datensätze tablet und correspondent hinzufügen und dabei zum Beispiel gibt einfach eine leere Liste zurück, wie Sie es erwarten würden. Wenn ich manuell eine Zeile in meine tablet_correspondent Tabelle unter Verwendung der vorhandenen IDs für Tablets und Korrespondenten einfüge, wird die Liste wie erwartet erneut ausgefüllt.

Wenn ich jedoch

cor = Correspondent.query.first() 
tab = Tablet.query.first() 
tab.correspondents.append(cor) 

zu tun versuchen, erhalte ich:

KeyError: 'tablet_correspondents' 

Ich bin mir ziemlich sicher, dass ich etwas ziemlich elementar hier auslassen.

+0

Ich denke, Sie '__tablename__ =„tablet_correspondent“' falsch ist.Sollte es nicht "tablet_correspondents" sein (mit einem 's' am Ende)? –

Antwort

10

Das Problem mit Ihrem Code ist in der .__init__ Methode. Wenn Sie die Parameter debug-watch/print() sind, werden Sie feststellen, dass der Parameter tablet ist eigentlich eine Instanz von Correspondent:

class Tablet_Correspondent(db.Model): 
    def __init__(self, tablet=None, correspondent=None): 
     print "in __init__: ", tablet, correspondent 
     self.tablet = tablet 
     self.correspondent = correspondent 

Der Grund dafür ist die Art und Weise SA neue Werte schafft. Creation of New Values von Dokumentation:

Wenn eine Liste append() Ereignis (oder einen Satz add(), Wörterbuch SetItem() oder Skalar Zuordnung Ereignis) durch den Verein Proxy abgefangen wird, es instanziiert eine neue Instanz des "intermediate" -Objekts unter Verwendung seines -Konstruktors, wobei der angegebene Wert als einzelnes Argument übergeben wird.

In Ihrem Fall, wenn Sie tab.correspondents.append(cor) aufrufen, wird die Tablet_Correspondent.__init__ mit einziges Argument cor genannt.

Lösung? Wenn Sie nur Correspondents zur Tablet hinzufügen, dann schalten Sie einfach die Parameter in __init__ um. Entfernen Sie den zweiten Parameter vollständig.
Wenn Sie jedoch auch cor.tablets.append(tab) verwenden möchten, dann müssen Sie explizit auf die association_proxy das creator Argument verwenden, wie oben in der Dokumentation verknüpft erklärt:

class Tablet(db.Model, GlyphMixin): 
    # ... 
    correspondents = association_proxy('tablet_correspondents', 'correspondent', creator=lambda cor: Tablet_Correspondent(correspondent=cor)) 

class Correspondent(db.Model, GlyphMixin): 
    # ... 
    tablets = association_proxy('correspondent_tablets', 'tablet', creator=lambda tab: Tablet_Correspondent(tablet=tab)) 
+0

Fantastisch. Vielen Dank. – urschrei

1

Wie van sagte, das Problem Aufenthalt in der __init__ Methode der Association Object.

In der Tat, wenn Tablet oder Correspondent Klassen keine __init__ Methode definieren oder keinen Parameter übergeben, funktioniert die Lösung nicht (kein Argument erwartet).

Ich fand eine alternative Lösung. Es ist leicht zu erkennen, welche Klasse proxied werden muss, so kann es auf das rechte Feld zugeordnet wird (und die Arbeit noch mehr Verbände auf Hinzufügen):

class Tablet_Correspondent(db.Model): 

    # ... 

    def __init__(self, proxied=None): 
     if type(proxied) is Tablet: 
      self.tablet = proxied 
     elif type(proxied) is Correspondent: 
      self.correspondent = proxied 
+0

Schöne Möglichkeit, lambdas zu vermeiden. Vielen Dank! –