2015-01-06 78 views
4

如何最好地實現多對多,自引用,非對稱關係SQLAlchemy的?我想使用一個關聯對象(讓我們把這個類稱爲「關注」),這樣我就可以擁有與關係相關的其他屬性。通過SqlAlchemy中的關聯對象實現多對多,自引用,非對稱關係(twitter模型)

我見過很多使用關聯表的例子,但沒有一個像我上面描述的那樣。這是我到目前爲止:

class UserProfile(Base): 
    __tablename__ = 'user' 

    id = Column(Integer, primary_key=True) 
    full_name = Column(Unicode(80)) 
    gender = Column(Enum(u'M',u'F','D', name='gender'), nullable=False) 
    description = Column(Unicode(280)) 
    followed = relationship(Follow, backref="followers") 

class Follow(Base): 
    __tablename__ = 'follow' 

    follower_id = Column(Integer, ForeignKey('user.id'), primary_key=True) 
    followee_id = Column(Integer, ForeignKey('user.id'), primary_key=True) 
    status = Column(Enum(u'A',u'B', name=u'status'), default=u'A') 
    created = Column(DateTime, default=func.now()) 
    followee = relationship(UserProfile, backref="follower") 

想法?

回答

4

這已經是差不多回答於here。在這裏,通過利用裸鏈表製作的多對多的優點得到了改進。

我不是在SQL好,無論是在SQLAlchemy的,但因爲我心中有一段較長的時間這個問題,我試圖找到同時具有優勢的解決方案:有附加屬性的關聯對象和直接像一個裸鏈接表(它沒有爲關聯提供一個對象)。由運的其他建議刺激了以下似乎安靜對我很好:

#!/usr/bin/env python3 
# coding: utf-8 

import sqlalchemy as sqAl 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import sessionmaker, relationship, backref 
from sqlalchemy.ext.associationproxy import association_proxy 

engine = sqAl.create_engine('sqlite:///m2m-w-a2.sqlite') #, echo=True) 
metadata = sqAl.schema.MetaData(bind=engine) 

Base = declarative_base(metadata) 

class UserProfile(Base): 
    __tablename__ = 'user' 

    id   = sqAl.Column(sqAl.Integer, primary_key=True) 
    full_name  = sqAl.Column(sqAl.Unicode(80)) 
    gender  = sqAl.Column(sqAl.Enum('M','F','D', name='gender'), default='D', nullable=False) 
    description = sqAl.Column(sqAl.Unicode(280)) 
    following  = association_proxy('followeds', 'followee') 
    followed_by = association_proxy('followers', 'follower') 

    def follow(self, user, **kwargs): 
    Follow(follower=self, followee=user, **kwargs) 

    def __repr__(self): 
    return 'UserProfile({})'.format(self.full_name) 

class Follow(Base): 
    __tablename__ = 'follow' 

    followee_id = sqAl.Column(sqAl.Integer, sqAl.ForeignKey('user.id'), primary_key=True) 
    follower_id = sqAl.Column(sqAl.Integer, sqAl.ForeignKey('user.id'), primary_key=True) 
    status  = sqAl.Column(sqAl.Enum('A','B', name=u'status'), default=u'A') 
    created  = sqAl.Column(sqAl.DateTime, default=sqAl.func.now()) 
    followee  = relationship(UserProfile, foreign_keys=followee_id, backref='followers') 
    follower  = relationship(UserProfile, foreign_keys=follower_id, backref='followeds') 

    def __init__(self, followee=None, follower=None, **kwargs): 
    """necessary for creation by append()ing to the association proxy 'following'""" 
    self.followee = followee 
    self.follower = follower 
    for kw,arg in kwargs.items(): 
     setattr(self, kw, arg) 

Base.metadata.create_all(engine, checkfirst=True) 
session = sessionmaker(bind=engine)() 

def create_sample_data(sess): 
    import random 
    usernames, fstates, genders = ['User {}'.format(n) for n in range(4)], ('A', 'B'), ('M','F','D') 
    profs = [] 
    for u in usernames: 
    user = UserProfile(full_name=u, gender=random.choice(genders)) 
    profs.append(user) 
    sess.add(user) 

    for u in [profs[0], profs[3]]: 
    for fu in profs: 
     if u != fu: 
     u.follow(fu, status=random.choice(fstates)) 

    profs[1].following.append(profs[3]) # doesn't work with followed_by 

    sess.commit() 

# uncomment the next line and run script once to create some sample data 
# create_sample_data(session) 

profs = session.query(UserProfile).all() 

print(  '{} follows {}: {}'.format(profs[0], profs[3], profs[3] in profs[0].following)) 
print('{} is followed by {}: {}'.format(profs[0], profs[1], profs[1] in profs[0].followed_by)) 

for p in profs: 
    print("User: {0}, following: {1}".format(
    p.full_name, ", ".join([f.full_name for f in p.following]))) 
    for f in p.followeds: 
    print(" " * 25 + "{0} follow.status: '{1}'" 
      .format(f.followee.full_name, f.status)) 
    print("   followed_by: {1}".format(
    p.full_name, ", ".join([f.full_name for f in p.followed_by]))) 
    for f in p.followers: 
    print(" " * 25 + "{0} follow.status: '{1}'" 
      .format(f.follower.full_name, f.status)) 

似乎不可或缺定義爲Association Object兩個關係。 association_proxy方法似乎不適合自我指涉關係。 Follow構造函數的參數oder對我來說似乎不合邏輯,但只適用於這種方式(這是here的解釋)。

在第117頁上找到關於secondary -parameter到relationship()以下注釋書Rick Copeland - Essential Sqlalchemy

需要注意的是,如果你使用的SQLAlchemy對哆。瑪能力:N 關係,連接表應該只有用於將兩個 表連接在一起,而不用於存儲輔助屬性。如果您需要 使用中間連接表來存儲關係的附加屬性 ,則應該使用兩個1:N關係。

對不起,這有點冗長,但我喜歡可以直接複製,粘貼和執行的代碼。這適用於Python 3.4和SqlAlchemy 0.9,但也可能適用於其他版本。

+0

我遇到過這個帖子,發現它與我的問題最相關。但是,我想我更具體地尋找一種不涉及定義兩種關係的解決方案。我的解決方案中有兩個後續問題。首先,通過「Follow」或「UserProfile」類中的關聯代理可以實現類似的功能嗎?另外,爲什麼有必要明確定義聯接(primaryjoin和secondaryjoin)。 –

+0

我還沒有考慮過這個問題,我相信有幾種方法可以做得更好。 - 我不知道如何消除'user.id - > follow.follower_id'的賦值。 'user.id - > follow.followee_id'沒有顯式定義primaryjoin/secondardjoin。 – TNT

+0

@AndrewBurnett感謝您關於關聯代理的提示,當然還有問最初的問題(我投了贊成票)。它阻止了我幾個小時去做我昨天計劃的事情...... - 我刪除了額外的「直接」多對多關係,並添加了一個方便的「跟隨」方法。 – TNT

相關問題