2012-04-23 52 views
2

我试图让以下模型一起工作。首先的情况如下:彼此依赖的一对多关系SQLAlchemy

  1. 用户可以有多个电子邮件地址,但每个电子邮件地址只能与一个用户相关联;
  2. 每个用户只能有一个主电子邮件地址(认为它像他们当前的电子邮件地址)。

电子邮件地址是用户的ID,所以他们必须总是有一个,但是当他们改变它时,我想跟踪他们过去使用过的其他人。到目前为止,该设置是有一个助手表user_emails,其中包含一个电子邮件和用户之间的联系,我听说不应该设置为使用声明式SQLAlchemy方法的类(尽管我不知道为什么)。另外,我是否认为我需要使用use_alter=True,因为在插入之前,users表将不知道外键email_id

models.py看起来是这样的:

"""models.py""" 
user_emails = Table('user_emails', Base.metadata, 
       Column('user_id', Integer, ForeignKey('users.id'), 
         primary_key=True), 
       Column('email', String(50), ForeignKey('emails.address'), 
         primary_key=True)) 

class User(Base): 
    __tablename__ = 'users' 
    id = Column(Integer, Sequence('usr_id_seq', start=100, increment=1), 
       primary_key=True) 
    email_id = Column(String(50), 
         ForeignKey('emails.address', use_alter=True, name='fk_email_id'),    
         unique=True, nullable=False) 
    first = Column(String(25), unique=True, nullable=False) 
    last = Column(String(25), unique=True, nullable=False) 

    def __init__(self, first, last): 
     self.first = first 
     self.last = last 

class Email(Base): 
    __tablename__ = 'emails' 
    address = Column(String(50), unique=True, primary_key=True) 
    user = relationship(User, secondary=user_emails, backref='emails') 
    added = Column(DateTime, nullable=False) 
    verified = Column(Boolean, nullable=False) 

    def __init__(self, address, added, verified=False): 
     self.address = address 
     self.added = added 
     self.verified = verified 

似乎一切都OK,直到我尝试提交到DB:

>>> user = models.User("first", "last") 
>>> addy = models.Email("[email protected]", datetime.datetime.utcnow()) 
>>> addy 
<Email '[email protected]' (verified: False)> 
>>> user 
>>> <User None (active: True)> 
>>> 
>>> user.email_id = addy 
>>> user 
>>> <User <Email '[email protected]' (verified: False)> (active: True)> 
>>> Session.add_all([user, addy]) 
>>> Session.commit() 
>>> ... 
>>> sqlalchemy.exc.ProgrammingError: (ProgrammingError) can't adapt type 'Email' "INSERT INTO users (id, email_id, first, last, active) VALUES (nextval('usr_id_seq'), %(email_id)s, %(first)s, %(last)s, %(active)s) RETURNING users.id" {'last': 'last', 'email_id': <Email '[email protected]' (verified: False)>, 'active': True, 'first': 'first'} 

所以,我认为我做错了什么/愚蠢,但我是SQLAlchemy的新手,所以我不确定我需要做什么才能正确设置模型。

最后,假设我获得了正确的模型设置,是否可以添加一个关系,以便通过加载任意电子邮件对象,我可以从Email对象的属性访问拥有它的用户?

谢谢!

回答

5

你已经有了一个很好的解决方案,一个小的修复将使你的代码工作。查找下面的代码如下快速反馈:

  • 你需要use_alter=True?没有,你其实并不需要那个。如果在数据库级别上计算Email表的primary_key表(如同基于autoincrement的主键),那么当您有两个表时彼此相同时,您可能需要它。在你的情况下,你甚至没有,因为你有第三张桌子,所以对于任何关系组合,SA (sqlalchemy)将通过插入新的电子邮件,然后插入用户,然后插入关系。
  • 你的代码有什么问题?:那么,您正在将Email的实例分配给User.email_id,该实例仅应获得email值。有两种方法可以修复它:
    1. 直接分配电子邮件。因此将行user.email_id = addy更改为user.email_id = addy.address
    2. 创建relationship然后进行分配(请参阅下面的代码)。 个人而言,我更喜欢选项-2。
  • 其他东西:您当前的型号不检查User.email_id实际上是User.emails之一。这可能是由设计,但其他人只需添加一个ForeignKey[users.id, users.email_id] to [user_emails.user_id, user_emails.email]

示例代码版本2:

""" models.py """ 
class User(Base): 
    __tablename__ = 'users' 
    # ... 
    email_id = Column(String(50), 
         ForeignKey('emails.address', use_alter=True, 
           name='fk_email_id'), unique=True, 
         nullable=False) 
    default_email = relationship("Email", backref="default_for_user") 

""" script """ 
# ... (all that you have below until next line) 
# user.email_id = addy.address 
user.default_email = addy 
+0

这是一个很好的答案,谢谢!将'Email'分配给'User'中的'email_id'是我的一个错字 - 我想这解释了编程错误异常?我打算使用您的解决方案的第二版。 PS - 'use_alter'是否需要在给定的位置,正如你所说的,关键是地址(而不是自动增量?)。 – Edwardr 2012-04-24 08:33:35

+0

正如我所提到的,你的代码不需要'use_alter = True'。 – van 2012-04-24 08:38:43

3

我不熟悉Python/SQLAlchemy的,但这里是代表你在数据库中想要什么的一种方式:

enter image description here

你会要么使用延迟约束(如果你的DBMS支持它们),将USER.PRIMARY_EMAIL留空(如上面的模型所示)以中断数据修改周期。


或者,你可以做这样的事情:属于同一用户

enter image description here

电子邮件进行排序(注意备用钥匙:{USER_ID,ORDER}),和无论哪种电子邮件在订购之上都可以被视为“主要”。这种方法的好处是它完全避免了循环引用。

+0

虽然我认为我的问题已在上面解决,但第二种方法很有趣 - 谢谢。 – Edwardr 2012-04-24 08:36:46

1

http://docs.sqlalchemy.org/en/latest/orm/tutorial.html的例子是接近你想要什么。除非需要多对多关系,否则我从不使用像user_emails这样的中间连接表。用户到电子邮件应该是一对多的。

为了您的需要跟踪旧的电子邮件地址?为您的电子邮件类添加“过时的”布尔属性并对其进行过滤以显示当前或旧的电子邮件地址。

+0

+1:这实际上是一个非常好的点 - 它应该足以让没有帮助表的简单1-N关系。 – van 2012-04-25 16:29:17