[英]Sqlalchemy: One to Many relationship combined with Many to Many relationship
我有一個具有多對多關系的用戶和組表
_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類添加一個group_id列和一個關系,但這有缺點。 我想在調用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簡化此操作,但是如何強制每個用戶僅使用一個主組?
如何使用GroupMemberships模型代替_usergroup_table來保持關聯? 用戶可以通過“組成員身份”擁有多個組,並且組成員身份可以包含其他屬性,例如給定的組是否是關聯用戶的主要組。
編輯
為了強制每個用戶限制一個主要組,我將在User模型中使用驗證,這樣分配記錄的任何一次嘗試分配比一個主要組更多(或更少)的嘗試都會導致錯誤。 我不知道完全依靠數據庫的完整性系統來實現相同結果的方法。 驗證檢查的編碼方式有很多種- 文檔顯示了使用validates()裝飾器的一種不錯的方法。
您最初想到的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()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.