I am trying to model a very typical permission structure where a User model (the human user of my website) can be assigned to some Roles, and each of those Roles has some Permissions.
It would be really helpful if I could get a relationship from User straight to Permission. That way, I could fetch a user (instance) from the database and just do user.permissions to get his permissions, put some filtering to check if a user has a specific Permission, have them pre-loaded... in sum: all the goodies that come with a relationship.
A viewonly relationship would be perfectly fine. As mentioned by Mike Bayer in a very similar question, I can't be writing to User.permissions because we don't not know which 'Role' to use or where to insert it.
I have created two intermediate tables:
User -- M2M --> Role(s) -- M2M --> Permission(s)
| ^
+-------- user.permissions -----------+
users_rolesto connect theuserto itsrolesthrough their primary key (the ID)- and
roles_permissionsto connect eachroleto itspermissions,
This is my table structure (simplified for the question, but even the full version is really typical and... simple)
class User(DeclarativeBase, Mixin):
__tablename__ = 'users'
email = Column(String(25), nullable=False)
_users_roles = Table('users_roles', DeclarativeBase.metadata,
Column('user_id', ForeignKey('users.id', ...
Column('role_id', ForeignKey('roles.id', ...
PrimaryKeyConstraint('user_id', 'role_id',),
)
class Role(DeclarativeBase, Mixin):
__tablename__ = 'roles'
name = Column(Text(), nullable=False, unique=True)
users = relationship("User", secondary=_users_roles, backref="roles")
_roles_permissions = Table('roles_permissions', DeclarativeBase.metadata,
Column('role_id', ForeignKey('roles.id', ...
Column('permission_id', ForeignKey('permissions.id', ...
PrimaryKeyConstraint('role_id', 'permission_id',),
)
class Permission(DeclarativeBase, Mixin):
__tablename__ = 'permissions'
key = Column(Text, nullable=False, unique=True,)
I saw this other answer that looks very promising, yet I don't seem to be able to make it work. Honestly, I've been trying a lot of combinations and I'd say that the furthest I got was with this:
permissions = relationship('Permission',
secondary="""join(User, users_roles,
User.id == users_roles.c.user_id
).join(roles_permissions,
users_roles.c.role_id == roles_permissions.c.role_id
).join(
Permission, roles_permissions.c.permission_id == Permission.id
)""",
viewonly=True,
)
Which gives me this error:
sqlalchemy.exc.ArgumentError: Relationship User.permissions
could not determine any unambiguous local/remote column pairs
based on join condition and remote_side arguments. Consider
using the remote() annotation to accurately mark those elements
of the join condition that are on the remote side of the relationship.
Any hints would be greatly appreciated. Thank you in advance.