在SQLAlchemy中查询多对多关系

1 投票
3 回答
1791 浏览
提问于 2025-04-18 17:50

我有以下几个模型。一个用户可以有很多角色,而一个角色可以拥有很多权限。我现在有点搞不清楚该怎么查询才能得到我想要的结果。

user_role = db.Table(
    'user_role',
    db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
    db.Column('role_id', db.Integer, db.ForeignKey('role.id')),
    db.UniqueConstraint('user_id', 'role_id')
)

role_permission = db.Table(
    'role_permission', 
    db.Column('permission_id', db.Integer, db.ForeignKey('permission.id')),
    db.Column('role_id', db.Integer, db.ForeignKey('role.id')),
    db.UniqueConstraint('permission_id', 'role_id')
)

class Role(Base):
    __tablename__ = 'role'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), unique=True, nullable=False)

class Permission(Base):
    __tablename__ = 'permission'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)

    roles = db.relation(Role, secondary=role_permission, backref=db.backref('permissions'))

class User(Base, UserMixin):
    __tablename__ = 'user'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(60), unique=True, nullable=False)
    password_hash = db.Column(db.String(80), nullable=False)

    roles = db.relation(Role, secondary=user_role, backref=db.backref('users'))

我想要得到一个(最好是唯一的)用户所拥有的所有权限的列表,但我似乎找不到合适的方法来实现。

我可以通过在用户模型中创建一个生成器来获取这个列表:

def get_all_permissions(self):
    for role in self.roles:
        for perm in role.permissions:
            yield perm

不过我希望能通过一次查询就能完成这个操作。

3 个回答

0

如果你已经把用户对象连同权限和角色一起加载到内存中,那么你的代码应该能快速处理这些信息,而不需要再去数据库查找。

否则,你可以使用以下查询:

    user_id = 789
    permissions = (db.session.query(Permission)
            .join(Role, Permission.roles)
            .join(User, Role.users)
            .filter(User.id == user_id)
            ).distinct()
    #print(permissions)
    for perm in permissions:
        print(perm)
1

好吧,如果你只是想获取权限的列表,可以试试这样做:

permissions = session.query(Permission).\
    join(Role).join(User).filter(User.username='MisterX').all()

或者你可以根据需要进行筛选。如果想让权限变得唯一,可以使用分组:

permissions = session.query(Permission.id, Permission.name).join(Role).join(User).\
    filter(User.username='MisterX').group_by(Permission.id).all()

或者,如果可以的话,使用声明式扩展,而不需要特别的查询:

permissions = User.roles.permissions

这样有帮助吗?

1

可能是因为你没有指定元数据参数,所以你的关联表没有被正确识别。这个脚本对我来说是有效的:

#!/bin/python

from sqlalchemy import Table
from sqlalchemy import Integer, String, ForeignKey, create_engine, Column, PrimaryKeyConstraint
from sqlalchemy.orm import relationship, backref, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:///:memory:', echo=True)
Base = declarative_base()

user_role = Table(
    'user_role',
    Base.metadata,
    Column('user_id', Integer, ForeignKey('users.id')),
    Column('role_id', Integer, ForeignKey('roles.id')),
    PrimaryKeyConstraint('user_id', 'role_id')
)

role_permission = Table(
    'role_permission', 
    Base.metadata,
    Column('permission_id', Integer, ForeignKey('permissions.id')),
    Column('role_id', Integer, ForeignKey('roles.id')),
    PrimaryKeyConstraint('permission_id', 'role_id')
)

class Role(Base):
    __tablename__ = 'roles'

    id = Column(Integer, primary_key=True)
    name = Column(String(100), unique=True, nullable=False)

class Permission(Base):
    __tablename__ = 'permissions'

    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)

    roles = relationship("Role", secondary=role_permission, backref=backref('permissions'))

class User(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    username = Column(String(60), unique=True, nullable=False)
    password_hash = Column(String(80), nullable=False)

    roles = relationship("Role", secondary=user_role, backref=backref('users'))


Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()

u = User(username="user", password_hash="secret")

r1 = Role(name="Role 1")
session.add(r1)
r2 = Role(name="Role 2")
session.add(r2)

p1 = Permission(name="Permission 1")
p2 = Permission(name="Permission 2")
p3 = Permission(name="Permission 3")

r1.permissions.append(p1)
r1.permissions.append(p2)

r2.permissions.append(p2)
r2.permissions.append(p3)


u.roles.append(r1)
u.roles.append(r2)

session.add(u)

for perm in session.query(Permission).join(Role, Permission.roles).\
    join(User, Role.users).filter(User.username=="user").distict()all():
    print(perm.name)

撰写回答