我有一个表用户和一个表朋友将用户映射到其他用户,因为每个用户可以有很多朋友。这种关系显然是对称的:如果用户A是用户B的朋友,那么用户B也是用户A的朋友,我只存储一次这种关系。除了两个用户ID之外,Friends表还有其他字段,因此我必须使用关联对象。
我试图在Users类(扩展声明性基础)中以声明式样式定义这种关系,但我似乎无法弄清楚如何做到这一点。我希望能够通过房产朋友访问给定用户的所有朋友,所以说朋友= bob.friends。
解决此问题的最佳方法是什么?我试着在这里发布许多不同的设置,并且由于各种原因它们都没有工作。
编辑:我最近的尝试是这样的:class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
# Relationships
friends1 = relationship('Friends', primaryjoin=lambda: id==Friends.friend1ID)
friends2 = relationship('Friends', primaryjoin=lambda: id==Friends.friend2ID)
class Friends(Base):
__tablename__ = 'friends'
id = Column(Integer, primary_key=True)
friend1ID = Column(Integer, ForeignKey('users.id') )
friend2ID = Column(Integer, ForeignKey('users.id') )
status = Column(Integer)
# Relationships
vriend1 = relationship('Student', primaryjoin=student2ID==Student.id)
vriend2 = relationship('Student', primaryjoin=student1ID==Student.id)
然而,这会导致以下错误:
InvalidRequestError: Table 'users' is already defined for this MetaData instance. Specify 'extend_existing=True' to redefine options and columns on an existing Table object.
我必须承认,在这一点上,由于许多失败的尝试,我完全感到困惑,可能在上面犯了不止一个愚蠢的错误。
答案 0 :(得分:20)
通过重复定义类映射(例如,在交互式解释器中,或在可以多次调用的函数中)或通过混合声明式样式类,不止一次地描述表来引起该特定异常与表反射的映射。在前一种情况下,消除重复呼叫;如果你以交互方式进行,或者消除额外的函数调用(可能很好地用于singleton / borg对象),就启动一个新的解释器。
在后一种情况下,只需执行异常所说的内容,在类定义中添加__table_args__ = {'extend_existing': True}
作为额外的类变量。只有在您确实正确地将表格正确描述两次时才执行此操作,如表格反射。
答案 1 :(得分:1)
我使用Flask-SQLAlchemy时出现此错误,但其他解决方案无效。
错误只发生在我们的生产服务器上,而我的计算机和测试服务器上的一切运行正常。
我有一个'Model'类,我的所有其他数据库类都继承自:
class Model(db.Model):
id = db.Column(db.Integer, primary_key=True)
出于某种原因,ORM为从此类继承的类提供了与此类相同的表名称。也就是说,对于每个类,它试图为它创建一个表,称为表'model'。
解决方案是使用' tablename '类变量明确命名子表:
class Client(Model):
__tablename__ = "client"
email = db.Column(db.String)
name = db.Column(db.String)
address = db.Column(db.String)
postcode = db.Column(db.String)
答案 2 :(得分:0)
正如评论中所提到的,我更喜欢扩展模型,其中Friendship
本身就是一个实体,而朋友之间的链接却是独立的实体。通过这种方式,人们可以存储对称的属性以及不对称的属性(就像一个人对另一个人的看法)。因此,下面的模型应该显示我的意思:
...
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
# relationships
friends = relationship('UserFriend', backref='user',
# ensure that deletes are propagated
cascade='save-update, merge, delete',
)
class Friendship(Base):
__tablename__ = "friendship"
id = Column(Integer, primary_key=True)
# additional info symmetrical (common for both sides)
status = Column(String(255), nullable=False)
# @note: also could store a link to a Friend who requested a friendship
# relationships
parties = relationship('UserFriend',
back_populates='friendship',
# ensure that deletes are propagated both ways
cascade='save-update, merge, delete',
)
class UserFriend(Base):
__tablename__ = "user_friend"
id = Column(Integer, primary_key=True)
friendship_id = Column(Integer, ForeignKey(Friendship.id), nullable=False)
user_id = Column(Integer, ForeignKey(User.id), nullable=False)
# additional info assymmetrical (different for each side)
comment = Column(String(255), nullable=False)
# @note: one could also add 1-N relationship where one user might store
# many different notes and comments for another user (a friend)
# ...
# relationships
friendship = relationship(Friendship,
back_populates='parties',
# ensure that deletes are propagated both ways
cascade='save-update, merge, delete',
)
@property
def other_party(self):
return (self.friendship.parties[0]
if self.friendship.parties[0] != self else
self.friendship.parties[1]
)
def add_friend(self, other_user, status, comment1, comment2):
add_friendship(status, self, comment1, other_user, comment2)
# helper method to add a friendship
def add_friendship(status, usr1, comment1, usr2, comment2):
""" Adds new link to a session. """
pl = Friendship(status=status)
pl.parties.append(UserFriend(user=usr1, comment=comment1))
pl.parties.append(UserFriend(user=usr2, comment=comment2))
return pl
通过这种方式,添加友谊非常容易
更新它的任何属性也是如此。您可以创建更多辅助方法,例如add_friend
使用上面的cascade
配置删除,User or Friendship or UserFriend
将确保删除双方。
选择所有朋友都可以随心所欲:print user.friends
此解决方案的真正问题是确保每个UserFriend
只有2 Friendship
个链接。同样,当从代码中操作对象时,它应该不是问题,但如果有人直接在SQL端导入/操作某些数据,则数据库可能会不一致。