【问题标题】:Many-to-many, self-referential, non-symmetrical relationship (twitter model) via Association Object in SqlAlchemy通过 SqlAlchemy 中的关联对象实现多对多、自引用、非对称关系(twitter 模型)
【发布时间】:2015-03-03 16:10:54
【问题描述】:

如何在 SqlAlchemy 中最好地实现多对多、自引用、非对称关系(想想 Twitter)?我想使用一个关联对象(我们称这个类为“Follow”),这样我就可以拥有与关系关联的其他属性。

我见过很多使用关联表的例子,但没有一个像我上面描述的那样。到目前为止,这是我所拥有的:

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")

想法?

【问题讨论】:

    标签: python sqlalchemy many-to-many relationship


    【解决方案1】:

    here 已经几乎回答了这个问题。在这里,这通过具有使用裸链接表制作的 多对多 的优点得到了改进。

    我不擅长 SQL,也不擅长 SqlAlchemy,但由于我已经考虑了这个问题很长时间,所以我试图找到一个具有两个优点的解决方案:具有附加属性的关联对象和 direct 关联就像一个裸链接表(它本身不为关联提供对象)。受到操作员的其他建议的刺激,以下内容对我来说似乎很安静:

    #!/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 中有解释)。

    在书 Rick Copeland - Essential Sqlalchemy 的第 117 页中,您可以找到以下关于 secondary 参数到 relationship() 的注释:

    注意,如果你使用 SQLAlchemy 的能力来做 M:N 关系,连接表应该用于连接两者 表在一起,不存储辅助属性。如果你需要 使用中间连接表来存储 关系,你应该改用两个 1:N 关系。

    抱歉,这有点冗长,但我喜欢可以直接复制、粘贴和执行的代码。这适用于 Python 3.4 和 SqlAlchemy 0.9,但也可能适用于其他版本。

    【讨论】:

    • 我遇到了那个帖子,发现它与我的问题最相关。但是,我想我更具体地寻找不涉及定义这两种关系的解决方案。对于您的解决方案,我确实有两个后续问题。首先,通过“Follow”或“UserProfile”类上的关联代理不能实现类似的功能吗?另外,为什么需要显式定义连接(primaryjoin 和 secondaryjoin)。
    • 我还在考虑它,我确信有几种方法可以做得更好。 -- 我不知道如何消除user.id -> follow.follower_id resp 的分配歧义。 user.id -> follow.followee_id 没有明确定义primaryjoin/secondardjoin。
    • @AndrewBurnett 感谢您对关联代理的提示,当然也感谢您提出最初的问题(我投了赞成票)。它让我有好几个小时无法完成我昨天计划的事情...... - 我删除了额外的“直接”多对多 relation 并添加了一个方便的 follow 方法。
    • 为了从双方添加关系(请参阅代码中的注释:# doesn't work with follow_by),您需要使用creator参数来@ 987654337@ 这样SA 知道如何创建Follow 实例:following = association_proxy('followeds', 'followee', creator=lambda followee: Follow(followee=followee)); followed_by = association_proxy('followers', 'follower', creator=lambda follower: Follow(followee=follower))
    • @AndrewBurnett 根据docs prof1.following.append(prof2) 等同于prof1.followeds.append(Follow(user2))append 方法中follower 属性 是“已分配”(文档说 argument 但这对我来说似乎并不准确)。因此,据我了解,即使selfcreator lambda 中有一个有效(且正确)的值,它也会被append 方法覆盖。
    猜你喜欢
    • 1970-01-01
    • 2021-05-31
    • 2018-10-02
    • 2015-03-16
    • 1970-01-01
    • 2011-11-10
    • 1970-01-01
    • 2018-11-24
    相关资源
    最近更新 更多