0

我需要实现一个“相关项目”功能,即允许同一个表中的项目以多对多的方式任意链接。类似于新闻网站显示相关文章的方式。

另外,我需要这种关系是双向的,如下所示:

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 中的其他东西。

4

2 回答 2

1

使用属性事件应该可以完成这项工作。请参阅下面的示例代码,其中一小段丑陋的代码仅用于避免无休止的递归:

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
于 2012-06-27T12:53:04.403 回答
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)
于 2012-06-28T01:41:37.280 回答