2012-06-18 18 views
6

Tengo dos tablas, tablet y correspondent:KeyError al añadir objetos a SQLAlchemy objeto de asociación

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 

I se puede agregar registros a tablet y correspondent, y haciendo por ejemplo Tablet.query.first().correspondents simplemente devuelve una lista vacía, como era de esperar. Si inserto manualmente una fila en mi tabla tablet_correspondent utilizando la tableta existente y las ID de corresponsalía, la lista se rellena, nuevamente como era de esperar.

Sin embargo, si trato de hacer

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

me sale:

KeyError: 'tablet_correspondents' 

Estoy bastante seguro de que estoy dejando fuera algo bastante elemental aquí.

+0

Creo que su '__tablename__ = "tablet_correspondent"' es erróneo.¿No debería ser 'tablet_correspondents' (con' s' al final)? –

Respuesta

10

El problema con su código está en el método .__init__. Si usted va a debug-watch/print() los parámetros, se dará cuenta que el parámetro tablet es en realidad una instancia de Correspondent:

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

La razón de esto es la forma SA crea nuevos valores. A partir de la documentación Creation of New Values:

Cuando un anexar lista() evento (o conjunto add(), diccionario SetItem(), o caso asignación escalar) es interceptada por el proxy asociación, que instancia una nueva instancia del objeto "intermediario" utilizando su constructor , pasando como un único argumento el valor dado.

En su caso, cuando se llama a tab.correspondents.append(cor), la Tablet_Correspondent.__init__ se llama con el solo argumento cor.

¿Solución? Si solo va a agregar Correspondents al Tablet, simplemente cambie los parámetros en el __init__. De hecho, elimine completamente el segundo parámetro.
Si, sin embargo, también se va a utilizar cor.tablets.append(tab), entonces es necesario utilizar explícitamente el argumento creator a la association_proxy como se explica en la documentación vinculada a la anterior:

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

Fantástico. Muchas gracias. – urschrei

1

como Van dijo, la estancia problema en el __init__ método del Association Object.

De hecho, si las clases de Tableta o Correspondiente no definen un método __init__ o no pasan ningún parámetro, la solución no funciona (no se espera ningún argumento).

Encontré una solución alternativa. Es fácil detectar qué clase tiene que ser proxy, por lo que se puede asignar al campo derecho (y todavía trabajan en la adición de más asociaciones):

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

Una buena manera de evitar el uso de lambdas. ¡Gracias! –

Cuestiones relacionadas