【问题标题】:Sqlalchemy: One to Many relationship combined with Many to Many relationshipSqlalchemy:一对多关系与多对多关系相结合
【发布时间】:2012-06-14 07:47:11
【问题描述】:

我有一个具有多对多关系的用户和组表

_usergroup_table = db.Table('usergroup_table', db.metadata,
    db.Column('user_id',  db.Integer, db.ForeignKey('user.id')),
    db.Column('group_id', db.Integer, db.ForeignKey('group.id')))

class User(db.Model):
    """Handles the usernames, passwords and the login status"""
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), nullable=False, unique=True)

class Group(db.Model):
    """Used for unix-style access control."""
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), nullable=False)
    users = db.relationship('User', secondary=_usergroup_table,
                            backref='groups')

现在我想向用户类添加一个主要组。当然,我可以只添加一个 group_id 列和与 Group 类的关系,但这有缺点。我想在调用 User.group 时获取所有组,包括 primary_group。主要组应始终是组关系的一部分。

编辑:

看来要走的路是关联对象

class User(db.Model, UserMixin):
    """Handles the usernames, passwords and the login status"""
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), nullable=False, unique=True)

    primary_group = db.relationship(UserGroup,
        primaryjoin="and_(User.id==UserGroup.user_id,UserGroup.primary==True)")

class Group(db.Model):
    """Used for unix-style access control."""
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(60), nullable=False)

class UserGroup(db.Model):
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
    active = db.Column(db.Boolean, default=False)

    user = db.relationship(User, backref='groups', primaryjoin=(user_id==User.id))
    group = db.relationship(Group, backref='users', primaryjoin=(group_id==Group.id))

我可以使用 AssociationProxy 简化此操作,但如何强制每个用户只使用一个主要组?

【问题讨论】:

    标签: python sqlalchemy flask-sqlalchemy


    【解决方案1】:

    如何使用 GroupMemberships 模型来保存关联,而不是 _usergroup_table?一个用户可以通过 Group Memberships 拥有多个组,并且一个组成员资格可以包含其他属性,例如给定的 Group 是否是关联用户的主要组。

    编辑

    为了强制每个用户一个主要组的限制,我将在用户模型中使用验证,这样任何尝试分配多于(或少于)一个主要组的尝试都会在记录时导致错误保存。我不知道有一种方法可以完全依靠数据库的完整性系统来实现相同的结果。有多种编码验证检查的方法 - documentation 展示了使用 validates() 装饰器的好方法。

    【讨论】:

    • 我同意,解决这个问题的方法是使用association
    【解决方案2】:

    您最初想到的 group_id 方法与“布尔标志”方法相比有几个优点。

    一方面,它自然受到限制,因此每个用户只有一个主要组。另一方面,加载 user.primary_group 意味着 ORM 可以通过它的主键来识别这个相关的行,并且可以在本地查看它的身份映射,或者通过主键发出一个简单的 SELECT,而不是发出一个具有硬性的查询to-index WHERE 子句,其中包含一个布尔值。另一个是无需进入关联对象模式,它简化了关联表的使用,并允许 SQLAlchemy 更有效地处理来自/向该表的加载和更新。

    下面我们使用事件,包括捕获“删除”事件的 @validates 的新版本(从 0.7.7 开始),以确保对 User.groups 和 User.primary_group 的对象级修改保持同步。 (如果在 0.7 的旧版本上,您可以使用属性“remove”事件或“AttributeExtension.remove”扩展方法,如果您仍在使用 0.6 或更早版本)。如果您想在数据库级别强制执行此操作,您可以使用触发器来验证您正在寻找的完整性:

    from sqlalchemy import *
    from sqlalchemy.orm import *
    from sqlalchemy.ext.declarative import declarative_base
    
    Base= declarative_base()
    
    _usergroup_table = Table('usergroup_table', Base.metadata,
        Column('user_id',  Integer, ForeignKey('user.id')),
        Column('group_id', Integer, ForeignKey('group.id')))
    
    class User(Base):
        __tablename__ = 'user'
        id = Column(Integer, primary_key=True)
        name = Column(String(60), nullable=False, unique=True)
        group_id = Column(Integer, ForeignKey('group.id'), nullable=False)
        primary_group = relationship("Group")
    
        @validates('primary_group')
        def _add_pg(self, key, target):
            self.groups.add(target)
            return target
    
        @validates('groups', include_removes=True)
        def _modify_groups(self, key, target, is_remove):
            if is_remove and target is self.primary_group:
                del self.primary_group
            return target
    
    class Group(Base):
        __tablename__ = 'group'
        id = Column(Integer, primary_key=True)
        name = Column(String(60), nullable=False)
        users = relationship('User', secondary=_usergroup_table,
                                backref=backref('groups', collection_class=set))
    
    e = create_engine("sqlite://", echo=True)
    Base.metadata.create_all(e)
    
    s = Session(e)
    
    g1, g2, g3 = Group(name='g1'), Group(name='g2'), Group(name='g3')
    u1 = User(name='u1', primary_group=g1)
    
    u1.groups.update([g2, g3])
    
    s.add_all([
        g1, g2, g3, u1
    ])
    s.commit()
    
    u1.groups.remove(g1)
    assert u1.primary_group is None
    u1.primary_group = g2
    s.commit()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-30
      • 2018-03-16
      • 2016-06-15
      • 2021-08-31
      • 1970-01-01
      • 2013-02-21
      • 2023-03-04
      相关资源
      最近更新 更多