2012-06-18 88 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的好方法。谢谢! –