0

多対多の関係を持つユーザーとグループのテーブルがあります

_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 を使用してこれを単純化することはできますが、ユーザーごとに 1 つのプライマリ グループのみを強制するにはどうすればよいでしょうか?

4

2 に答える 2

2

_usergroup_table の代わりに、関連付けを保持する GroupMemberships モデルはどうですか? ユーザーは、グループ メンバーシップを通じて多くのグループを持つことができ、グループ メンバーシップは、特定のグループが関連付けられたユーザーのプライマリ グループであるかどうかなど、追加の属性を保持できます。

編集

ユーザーごとに 1 つのプライマリ グループの制限を適用するために、User モデルで検証を使用して、1 つより多い (または少ない) プライマリ グループを割り当てようとすると、レコードの保存時にエラーが発生するようにします。データベースの整合性システムのみに依存して同じ結果を達成する方法を知りません。検証チェックをコーディングする方法はいくつもあります。ドキュメントには、validates() デコレータを使用した優れたアプローチが示されています。

于 2012-06-11T12:05:36.953 に答える
2

あなたが最初に考えた group_id アプローチには、ここで「ブールフラグ」アプローチにいくつかの利点があります。

1 つには、ユーザーごとに 1 つのプライマリ グループしか存在しないように、当然のことながら制約されます。別の例では、user.primary_group をロードすることは、ORM がこの関連する行をその主キーによって識別できることを意味し、ID マップでローカルに検索するか、主キーによる単純な SELECT を発行することができます。内部にブール値を持つ to-index WHERE 句。もう 1 つの理由は、関連オブジェクト パターンに入る必要がないことです。これにより、関連テーブルの使用が簡素化され、SQLAlchemy がこのテーブルとの間の読み込みと更新をより効率的に処理できるようになります。

以下では、「削除」イベントをキャッチする @validates の新しいバージョン (0.7.7 以降) を含むイベントを使用して、User.groups と User.primary_group へのオブジェクトレベルの変更が同期されていることを確認します。(古いバージョンの 0.7 では、属性の "remove" イベントまたは "AttributeExtension.remove" 拡張メソッド (まだ 0.6 以前の場合) を使用できます)。これを DB レベルで強制したい場合は、トリガーを使用して、探している整合性を検証できます。

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()
于 2012-06-12T22:49:59.610 に答える