我有一个具有多对多关系的用户和组表
_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简化此操作,但如何为每个用户仅强制使用一个主要组?
答案 0 :(得分:2)
GroupMemberships模型如何保存关联,而不是_usergroup_table?用户可以通过组成员身份拥有多个组,组成员身份可以包含其他属性,例如给定组是否是关联用户的主要组。
修改
为了强制每个用户限制一个主要组,我将在用户模型中使用验证,这样当记录为保存。我不知道如何完全依赖数据库的完整性系统来实现相同的结果。有许多方法可以对验证检查进行编码 - documentation使用validates()装饰器显示了一种很好的方法。
答案 1 :(得分:2)
你最初想到的group_id方法在这里对于“布尔标志”方法有几个优点。
首先,它自然受到约束,因此每个用户只有一个主要组。另一方面,加载user.primary_group意味着ORM可以通过它的主键识别该相关行,并且可以在身份映射中本地查找它,或者通过主键发出简单的SELECT,而不是发出具有硬键的查询。 to-index WHERE子句,里面有一个boolean。另一个是没有必要进入关联对象模式,这简化了关联表的使用,并允许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()