2012-02-02 44 views
16

私はSQLAlchemyの宣言を使って自己参照多対多関係を実装しようとしています。同じ属性を参照するSQLAlchemy ORMで自己参照多対多リレーションシップをどのように達成できますか?

この関係は、2人のユーザー間の友情を表します。オンラインどのようにして役割が分かれているのか、自己参照のm2m関係を作る方法(ドキュメントとGoogleの両方)が見つかりました。つまり、このm2mの関係では、UserAはUserBの上司などであるため、「下位」属性の下にリストするか、何を持っているかを示します。同様に、UserBはUserAを '上司'の下に表示します。

私たちは、このように同じテーブルに後方参照を宣言することができるので、これは、何の問題を構成するものではありません:

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

だから、当然のことながら、「上司」属性は、クラス内で明示的ではありません。

とにかく、ここに私の問題があります: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をbefriends場合ので、関係の役割は同じであり、私はBの友人を呼び出す場合、私はそれででリストを取得する必要があります。これは、完全な形で問題のあるコードです:これはあまりにも多くのテキストがある場合

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 
          ) 

申し訳ありませんが、私はこれで私はできる限り明示的になりたいです。私はWeb上でこれに関する参考資料を見つけることができないようです。

答えて

15

私は今日のメーリングリストで私が思いついたUNIONのアプローチです。

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 
+0

これは少し誤りのようです:誤って「all_friends」に追加すると、警告が表示されません。助言がありますか? – Halst

+0

これはまた、(1,2,2のような)交換されたIDと重複する友人関係を可能にします。あるIDが別のIDよりも大きいという制約を置くことができますが、次にどのユーザーがどのユーザーに 'friends'属性を追加できるかを把握する必要があります。 – Halst

+1

viewonly = Trueは、Pythonのコレクションの動作に影響しません。このコレクションへの追加を本当に心配しているのであれば、collection_clsを使用して、NotImplementedErrorまたはそれに類するものを投げるように変更された突然変異メソッドを持つリストまたはセット型を適用できます。 – zzzeek

4

私はこの同じ問題を解決するために必要と多対多の私もFriendクラスでUserクラスをサブクラス化し、sqlalchemy.orm.exc.FlushErrorに実行していた前記関係自己参照を持つ非常に多くについて台無し。最後に、自己参照多対多関係を作成する代わりに、結合表(または副表)を使用して自己参照の一対一の関係を作成しました。

自己参照オブジェクトで考えると、1対多の多対多です。それは元の質問のバックリファレンスの問題を解決しました。

実際に見たい場合は、working exampleもあります。また、それはgithubのように、ipythonノートブックを含んでいるような気がします。きちんとした

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 
関連する問題