2

我试图让以下模型一起工作。首先,场景如下:

  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

在我尝试提交数据库之前,一切似乎都很好:

>>> user = models.User("first", "last")
>>> addy = models.Email("example@example.com", datetime.datetime.utcnow())
>>> addy
<Email 'example@example.com' (verified: False)>
>>> user
>>> <User None (active: True)>
>>>
>>> user.email_id = addy
>>> user
>>> <User <Email 'example@example.com' (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 'example@example.com' (verified: False)>, 'active': True, 'first': 'first'}

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

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

谢谢!

4

3 回答 3

5

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

  • 你需要use_alter=True吗?不,你实际上不需要那个。如果表primary_keyEmail是在数据库级别计算的(与autoincrement基于 - 的主键一样),那么当您有两个foreign_keys相互连接的表时可能需要它。在您的情况下,您甚至没有那个,因为您有第三个表,因此对于任何关系组合,SA (sqlalchemy)将通过插入新的电子邮件、然后是用户、然后是关系来解决。
  • 你的代码有什么问题?:好吧,您正在分配一个实例,该实例Email应该User.email_id只获取该email值。有两种方法可以解决它:
    1. 直接分配电子邮件。所以将行更改user.email_id = addyuser.email_id = addy.address
    2. 创建一个relationship然后进行分配(参见下面的代码)。就个人而言,我更喜欢选项2。
  • User.email_id其他事情:您当前的模型不会检查User.emails. 这可能是设计使然,否则只需添加一个ForeignKeyfrom[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
于 2012-04-24T06:59:32.627 回答
3

我对 Python/SQLAlchemy 不熟悉,但这里有一种方法可以在数据库中表示您想要的内容:

在此处输入图像描述

您可以使用延迟约束(如果您的 DBMS 支持它们),或者让 USER.PRIMARY_EMAIL 为空(如上面的模型所示)来中断数据修改周期。


或者,您可以执行以下操作:

在此处输入图像描述

属于同一用户的电子邮件是有序的(注意备用键:{USER_ID, ORDER}),并且在该排序之上的任何电子邮件都可以被认为是“主要的”。这种方法的好处是它完全避免了循环引用。

于 2012-04-23T23:27:04.743 回答
1

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

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

于 2012-04-24T15:52:58.107 回答