如何在SQLAlchemy ORM上实现对同一属性的自引用多对多关系?

2024-05-20 20:46:52 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图在SQLAlchemy上使用声明式实现一个自引用多对多关系。

这种关系代表了两个用户之间的友谊。在网上,我发现(在文档和谷歌中)如何建立一个自我参照的m2m关系,其中的角色是不同的。这意味着在这种m2m关系中,举例来说,UserA是UserB的老板,所以他将他列在“下属”属性下,或者列在“你拥有什么”。同样,UserB在“superiors”下列出UserA。

这不构成问题,因为我们可以这样声明同一个表的backref:

subordinates = relationship('User', backref='superiors')

当然,这里的“superiors”属性在类中不是显式的。

不管怎样,这里是我的问题:如果我想要backref到我调用backref的同一个属性怎么办?像这样:

friends = relationship('User',
                       secondary=friendship, #this is the table that breaks the m2m
                       primaryjoin=id==friendship.c.friend_a_id,
                       secondaryjoin=id==friendship.c.friend_b_id
                       backref=??????
                       )

这是有道理的,因为如果A和B是朋友,那么关系角色是相同的,如果我调用B的朋友,我应该得到一个包含A的列表。这就是问题代码的全部内容:

friendship = Table(
    'friendships', Base.metadata,
    Column('friend_a_id', Integer, ForeignKey('users.id'), primary_key=True),
    Column('friend_b_id', Integer, ForeignKey('users.id'), primary_key=True)
)

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)

    friends = relationship('User',
                           secondary=friendship,
                           primaryjoin=id==friendship.c.friend_a_id,
                           secondaryjoin=id==friendship.c.friend_b_id,
                           #HELP NEEDED HERE
                           )

抱歉,如果这是太多的文字,我只想尽可能明确地与这个。我好像在网上找不到这方面的参考资料。


Tags: keyfriendid属性关系columnintegerusers
2条回答

我需要解决同样的问题,并且用自引用的多对多关系来处理很多事情,其中我还用一个Friend类来继承User类,并运行到sqlalchemy.orm.exc.FlushError。最后,我没有创建自引用的多对多关系,而是使用联接表(或辅助表)创建了自引用的一对多关系。

如果你考虑一下,对于自指对象,一对多就是多对多。它解决了原问题中的反引用问题。

我还有一个注册的working example如果你想看到它在运行。而且,现在看起来像是github格式化包含ipython笔记本的gist。整洁。

friendship = Table(
    'friendships', Base.metadata,
    Column('user_id', Integer, ForeignKey('users.id'), index=True),
    Column('friend_id', Integer, ForeignKey('users.id')),
    UniqueConstraint('user_id', 'friend_id', name='unique_friendships'))


class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String(255))

    friends = relationship('User',
                           secondary=friendship,
                           primaryjoin=id==friendship.c.user_id,
                           secondaryjoin=id==friendship.c.friend_id)

    def befriend(self, friend):
        if friend not in self.friends:
            self.friends.append(friend)
            friend.friends.append(self)

    def unfriend(self, friend):
        if friend in self.friends:
            self.friends.remove(friend)
            friend.friends.remove(self)

    def __repr__(self):
        return '<User(name=|%s|)>' % self.name

这是我今天早些时候在邮件列表中暗示的工会方法。

from sqlalchemy import Integer, Table, Column, ForeignKey, \
    create_engine, String, select
from sqlalchemy.orm import Session, relationship
from sqlalchemy.ext.declarative import declarative_base

Base= declarative_base()

friendship = Table(
    'friendships', Base.metadata,
    Column('friend_a_id', Integer, ForeignKey('users.id'), 
                                        primary_key=True),
    Column('friend_b_id', Integer, ForeignKey('users.id'), 
                                        primary_key=True)
)


class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String)

    # 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,
    )

    def __repr__(self):
        return "User(%r)" % self.name

# this relationship is viewonly and selects across the union of all
# friends
friendship_union = select([
                        friendship.c.friend_a_id, 
                        friendship.c.friend_b_id
                        ]).union(
                            select([
                                friendship.c.friend_b_id, 
                                friendship.c.friend_a_id]
                            )
                    ).alias()
User.all_friends = relationship('User',
                       secondary=friendship_union,
                       primaryjoin=User.id==friendship_union.c.friend_a_id,
                       secondaryjoin=User.id==friendship_union.c.friend_b_id,
                       viewonly=True) 

e = create_engine("sqlite://",echo=True)
Base.metadata.create_all(e)
s = Session(e)

u1, u2, u3, u4, u5 = User(name='u1'), User(name='u2'), \
                    User(name='u3'), User(name='u4'), User(name='u5')

u1.friends = [u2, u3]
u4.friends = [u2, u5]
u3.friends.append(u5)
s.add_all([u1, u2, u3, u4, u5])
s.commit()

print u2.all_friends
print u5.all_friends

相关问题 更多 >