我找到了许多关于如何使用单独的表或类创建自引用多对多关系(对于用户关注者或朋友)的解释:
以下是三个例子,一个来自Mike Bayer本人:
但是在我发现的每个例子中,在关系中定义primaryjoin
和secondaryjoin
的语法是一个早期绑定的语法:
# this relationship is used for persistence
friends = relationship("User", secondary=friendship,
primaryjoin=id==friendship.c.friend_a_id,
secondaryjoin=id==friendship.c.friend_b_id,
)
除了一种情况之外,这种方法很有用:当使用Base
类为所有对象定义id
列时,如文档中的Mixins: Augmenting the base所示
我的Base
类和followers
表是这样定义的:
from flask_sqlchalchemy import SQLAlchemy
db = SQLAlchemy()
class Base(db.Model):
__abstract__ = True
id = db.Column(db.Integer, primary_key=True)
user_flrs = db.Table(
'user_flrs',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id')))
但是现在我遇到了追随者关系的问题,这种关系在我将id
移动到mixin之前忠实地服务了我一段时间:
class User(Base):
__table_name__ = 'user'
followed_users = db.relationship(
'User', secondary=user_flrs, primaryjoin=(user_flrs.c.follower_id==id),
secondaryjoin=(user_flrs.c.followed_id==id),
backref=db.backref('followers', lazy='dynamic'), lazy='dynamic')
db.class_mapper(User) # trigger class mapper configuration
大概是因为id
不存在于本地范围内,尽管它似乎为此抛出一个奇怪的错误:
ArgumentError:无法在关系
'user_flrs.follower_id = :follower_id_1'
上找到涉及主要连接条件User.followed_users
的本地映射外键列的任何简单相等表达式。确保引用列与ForeignKey
或ForeignKeyConstraint
相关联,或者在连接条件中使用foreign()
注释进行注释。为了允许'=='
以外的比较运算符,该关系可以标记为viewonly=True
。
如果我将括号更改为引号以利用后期绑定,则会抛出相同的错误。我不知道如何使用foreign()
和remote()
来注释这个东西,因为我根本不知道sqlalchemy想要我描述为外国和远程的自我参照关系跨越次要表格!我尝试了很多这方面的组合,但到目前为止还没有奏效。
我有一个非常相似(虽然不完全相同)的自引用关系问题,它没有跨越一个单独的表,而关键只是将remote_side
参数转换为后期绑定参数。这对我来说很有意义,因为在早期绑定过程中不存在id
列。
如果我遇到麻烦并没有迟到,请告知。但是,在目前的范围内,我的理解是id
映射到Python内置id()
,因此不能作为早期绑定关系。
在连接中将id
转换为Base.id
会导致以下错误:
ArgumentError:无法在关系
'user_flrs.follower_id = "<name unknown>"'
上找到涉及主要连接条件User.followed_users
的本地映射外键列的任何简单相等表达式。确保引用列与ForeignKey
或ForeignKeyConstraint
相关联,或者在连接条件中使用foreign()
注释进行注释。为了允许'=='
以外的比较运算符,该关系可以标记为viewonly=True
。
你不能在你的连接过滤器中使用id
,不,因为那是built-in id()
function,而不是User.id
列。
你有三个选择:
User
模型后定义关系,将其分配给新的User
属性;你可以参考User.id
,因为它已从基地拉入:
class User(Base):
# ...
User.followed_users = db.relationship(
User,
secondary=user_flrs,
primaryjoin=user_flrs.c.follower_id == User.id,
secondaryjoin=user_flrs.c.followed_id == User.id,
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic'
)
relationship()
的任何参数在配置映射器时都被计算为Python表达式,而不仅仅是第一个参数:
class User(Base):
# ...
followed_users = db.relationship(
'User',
secondary=user_flrs,
primaryjoin="user_flrs.c.follower_id == User.id",
secondaryjoin="user_flrs.c.followed_id == User.id",
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic'
)
class User(Base):
# ...
followed_users = db.relationship(
'User',
secondary=user_flrs,
primaryjoin=lambda: user_flrs.c.follower_id == User.id,
secondaryjoin=lambda: user_flrs.c.followed_id == User.id,
backref=db.backref('followers', lazy='dynamic'),
lazy='dynamic'
)
对于后两个选项,请参阅sqlalchemy.orgm.relationship()
documentation:
relationship()
接受的一些参数可选地接受可调用函数,该函数在被调用时产生所需的值。父映射器在“映射器初始化”时调用可调用,这仅在首次使用映射器时发生,并且假定在构建了所有映射之后。这可用于解决声明顺序和其他依赖性问题,例如,如果Child
在同一文件中声明为Parent
* [。] *[...]
使用Declarative扩展时,Declarative初始值设定项允许将字符串参数传递给
relationship()
。这些字符串参数转换为callables,使用Declarative类注册表作为命名空间,将字符串计算为Python代码。这允许通过字符串名称自动查找相关类,并且无需将相关类导入到本地模块空间* [。] *[...]
- primaryjoin - [...]
primaryjoin
也可以作为可调用函数传递,该函数在mapper初始化时计算,并且在使用Declarative时可以作为Python可评估字符串传递。[...]
- 次级连接 - [...]
secondaryjoin
也可以作为可调用函数传递,该函数在mapper初始化时计算,并且在使用Declarative时可以作为Python可评估字符串传递。
字符串和lambda都定义了与第一个选项中使用的相同的user_flrs.c.followed_id == User.id
/ user_flrs.c.follower_id == User.id
表达式,但因为它们分别作为字符串和可调用函数给出,所以推迟评估,直到SQLAlchemy需要完成这些声明。