我需要实现“相关项目”功能,即允许来自同一个表的项目以多对多的方式任意地相互链接。类似于新闻网站显示相关文章的内容。
另外,我需要这种关系是双向的,如下所示:
a = Item()
b = Item()
a.related.append(b)
assert a in b.related # True
现在,在SQL级别上我想象这可以通过修改“标准”多对多关系来解决,因此每次建立关联时都会在关联表中插入2条记录,因此(a - > b)和(b - > a)是两个单独的记录。
或者,多对多表的连接条件可以某种方式检查关联的两侧,因此大致而不是... JOIN assoc ON a.id = assoc.left_id ...
SQLAlchemy会产生类似... JOIN assoc ON a.id = assoc.left_id OR a.id = assoc.right_id ...
有没有办法用SQLAlchemy配置它,所以关系的工作方式类似于“普通”的多对多关系?
我可能只是不知道正确的术语 - 我提出的所有内容 - “自我引用”,“双向”,“关联” - 用于描述SQLAlchemy中的其他内容。
答案 0 :(得分:1)
使用Attribute Events应该可以胜任。请参阅下面的示例代码,其中一些丑陋的代码仅用于避免无休止的递归:
class Item(Base):
__tablename__ = "item"
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
# relationships
related = relationship('Item',
secondary = t_links,
primaryjoin = (id == t_links.c.from_id),
secondaryjoin = (id == t_links.c.to_id),
)
_OTHER_SIDE = set()
from sqlalchemy import event
def Item_related_append_listener(target, value, initiator):
global _OTHER_SIDE
if not((target, value) in _OTHER_SIDE):
_OTHER_SIDE.add((value, target))
if not target in value.related:
value.related.append(target)
else:
_OTHER_SIDE.remove((target, value))
event.listen(Item.related, 'append', Item_related_append_listener)
# ...
a = Item()
b = Item()
a.related.append(b)
assert a in b.related # True
答案 1 :(得分:0)
为了完整起见,这是我最终得到的代码;监听器方法略有不同,以避免使用全局变量,也有remove
事件的监听器。
import sqlalchemy as sa
related_items = sa.Table(
"related_items",
Base.metadata,
sa.Column("id", sa.Integer, primary_key=True),
sa.Column("from_id", sa.ForeignKey("items.id")),
sa.Column("to_id", sa.ForeignKey("items.id")),
)
class Item(Base):
__tablename__ = 'items'
...
related = sa.orm.relationship('Item',
secondary = related_items,
primaryjoin = (id == related_items.c.from_id),
secondaryjoin = (id == related_items.c.to_id),
)
def item_related_append_listener(target, value, initiator):
if not hasattr(target, "__related_to__"):
target.__related_to__ = set()
target.__related_to__.add(value)
if target not in getattr(value, "__related_to__", set()):
value.related.append(target)
sa.event.listen(Item.related, 'append', item_related_append_listener)
def item_related_remove_listener(target, value, initiator):
if target in value.related:
value.related.remove(target)
sa.event.listen(Item.related, 'remove', item_related_remove_listener)