這已經是差不多回答於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,但也可能適用於其他版本。
我遇到過這個帖子,發現它與我的問題最相關。但是,我想我更具體地尋找一種不涉及定義兩種關係的解決方案。我的解決方案中有兩個後續問題。首先,通過「Follow」或「UserProfile」類中的關聯代理可以實現類似的功能嗎?另外,爲什麼有必要明確定義聯接(primaryjoin和secondaryjoin)。 –
我還沒有考慮過這個問題,我相信有幾種方法可以做得更好。 - 我不知道如何消除'user.id - > follow.follower_id'的賦值。 'user.id - > follow.followee_id'沒有顯式定義primaryjoin/secondardjoin。 – TNT
@AndrewBurnett感謝您關於關聯代理的提示,當然還有問最初的問題(我投了贊成票)。它阻止了我幾個小時去做我昨天計劃的事情...... - 我刪除了額外的「直接」多對多關係,並添加了一個方便的「跟隨」方法。 – TNT