2012-06-18 144 views
6

我有兩個表,tabletcorrespondentKeyError異常將對象添加到SQLAlchemy的關聯對象時

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 

我可以將記錄添加到tabletcorrespondent,和例如在做如您所料,Tablet.query.first().correspondents只是返回一個空列表。如果我使用現有的平板電腦和通訊員ID手動向我的tablet_correspondent表中插入一行,則會按照您的預期重新填充列表。

但是,如果我嘗試做

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

我得到:

KeyError: 'tablet_correspondents' 

我敢肯定,我離開了一些相當基本這裏。

+0

我想你'__tablename__ =「tablet_correspondent」'是錯誤的。它不應該是'tablet_correspondents'(最後是's')嗎? – 2012-06-18 23:31:57

回答

10

問題與您的代碼是在.__init__方法。如果你是debug-watch/print()的參數,你會發現,參數tablet實際上是Correspondent一個實例:

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

這樣做的原因是SA創造新價值的方式。從技術文檔Creation of New Values

當列表的append()事件(或設置的add(),字典setitem(),或 標分配事件)由協會代理截獲,它 實例化一個新的實例「中介」對象使用其構造函數 ,將給定值作爲單個參數傳遞。

在你的情況,當你調用tab.correspondents.append(cor),該Tablet_Correspondent.__init__被稱爲單參數cor

解決方案?如果您只將Correspondents添加到Tablet,那麼只需切換__init__中的參數即可。實際上,完全刪除第二個參數。
但是,如果你也將使用cor.tablets.append(tab),那麼你需要明確地使用creator參數來association_proxy爲鏈接到上面的文檔中解釋說:

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

太棒了。非常感謝。 – urschrei

1

像麪包車說,這個問題留在Association Object的方法__init__

事實上,如果平板電腦或通訊員類沒有定義__init__方法或未傳遞任何參數,則解決方案不起作用(不需要參數)。

我找到了一個替代解決方案。這很容易檢測哪些類有被代理,因此它可以被分配給右場(和增加更多的關聯仍然有效):

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

避免使用lambdas的好方法。謝謝! –