Flask-SQLAlchemy Query records by relationship attribute - python

In the app I've been working on uses Flask-User and their suggested models for authorization control. Here's an example:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
# User Authentication information
username = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(255), nullable=False, default='')
# User Email information
email = db.Column(db.String(255), nullable=False, unique=True)
confirmed_at = db.Column(db.DateTime())
# User information
is_enabled = db.Column(db.Boolean(), nullable=False, default=False)
# Relationships
roles = db.relationship('Role', secondary='user_roles',
backref=db.backref('users', lazy='dynamic'))
class Role(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), unique=True)
class UserRoles(db.Model):
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('role.id', ondelete='CASCADE'))
In addition, as part of the Flask-User/Flask-Login, current_user gives me a User object corresponding to the current user of the app (if logged in) and, as defined in our model, current_user.roles returns a list of Role objects, corresponding to the roles that are bound to the current user by the UserRoles table.
Now, at some point, I want to be able to query all users that have any of the roles the current user has. How can that be achieved given our current_user.roles list and my models in SQL Alchemy? How can I select all user where any of its User.roles match any of our current_user.roles?

Add __repr__ to Role as follows:
class Role(db.Model):
#...
def __repr__(self):
return self.id
And you can use following query to achieve your requirement.
User.query.filter(User.roles.any(Role.id.in_([role.id for role in current_user.roles]))).all()

Related

SQLAlchemy Association Proxy distinct right side

Given the models below, how can I ensure that the Users returned by the Document.users association proxy are distinct?
For example, the DocumentUser association table can have the follow data
document_id
user_id
role_id
Document_1
User_1
Role_1
Document_1
User_1
Role_2
Document_2
User_2
Role_1
To demo, the models are laid out as:
class Document(db.Model):
__tablename__ = 'documents'
id = db.Column(db.String, primary_key=True)
title = db.Column(db.String)
document_user_associations = db.relationship(
'DocumentUser',
lazy='select',
back_populates='document'
)
users = association_proxy('document_user_associations', 'user')
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.String, primary_key=True)
full_name = db.Column(db.String)
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.String, primary_key=True)
name = db.Column(db.String)
class DocumentUser(db.Model):
__tablename__ = 'document_users'
document_id = db.Column(db.String, db.ForeignKey('documents.id') primary_key=True)
user_id = db.Column(db.String, db.ForeignKey('users.id'), primary_key=True)
role_id = db.Column(db.String, db.ForeignKey('roles.id'), primary_key=True)
document = db.relationship('Document', back_populates='document_user_associations')
user = db.relationship('User')
role = db.relationship('Role')
If I were to do the following, assuming the data in the above table is persisted,
document_1 = db.session.query(Document).get(1)
print(document_1.users)
I would hope to see [<User User_1>] but instead I get [<User User_1>, <User User_1>] due to the association object. Is it possible using the association_proxy to return distinct Users given the above model?
EDIT:
In effect, it would be ideal if the association_proxy, or the association table relationship it relies on, to be able to group by the user, providing a list of the roles.

Querying data with nested relationships

I have a user table that has a relationship to my accounts table. My accounts table has a relationship to the account_permission table which is used to define permissions for accounts.
A user may have multiple accounts and he may have permissions to view his own accounts or other user's accounts.
I'm not sure how to query all the accounts that a user has and the accounts that he has permissions to view.
I thought that Account.query.filter_by(owner_id = user).all() would include permissions in it's results since it has a db relationship with a backref to the account_permission table but that is not the case.
# querying information
current_user = get_jwt_identity()
user = User.query.filter_by(email = current_user).first()
accounts = Account.query.filter_by(owner_id = user).all() # how do I get all the accounts that the user has permissions to?
# user model
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False)
password = db.Column(db.String(255), nullable=False)
isAdmin = db.Column(db.Boolean, default=False, nullable=False)
accounts = db.relationship('Account', backref='owner')
# accounts model
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from app import db, ma
class Account(db.Model):
account_id = db.Column(db.Integer, primary_key=True)
description = db.Column(db.String(255), nullable=False)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
permissions = db.relationship('AccountPermission', backref='permissions')
def __init__(self, account_id, description, owner_id):
self.account_id = account_id
self.description = description
self.owner_id = owner_id
class AccountSchema(ma.Schema):
class Meta:
fields = ('account_id', 'description', 'owner_id')
ordered = True
class AccountPermission(db.Model):
id = db.Column(db.Integer, primary_key=True)
account = db.Column(db.Integer, db.ForeignKey('account.account_id'))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
class AccountPermissionSchema(ma.Schema):
class Meta:
fields = ('account', 'user_id')
ordered = True

Use Flask-SqlAlchemy to query relationship database

I'm trying to use Flask-SQLAlchemy to query out database for the user profile page
So far I don't have a solution for this problem, only able to query all the User data by using users.query.all()
Each user has their own role_id, department_id, researchfield_id.
How can i query out all the Role, Department, ResearchField data that has relationship with User through ID?
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
password_hash = db.Column(db.String(128))
is_admin = db.Column(db.Boolean, default=False)
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
research_id = db.Column(db.Integer, db.ForeignKey('researchfields.id'))
class Department(db.Model):
__tablename__ = "departments"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
user = db.relationship('User', backref='department',
lazy='dynamic')
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
users = db.relationship('User', backref='role',
lazy='dynamic')
class ResearchField(db.Model):
__tablename__ = "researchfields"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60), index=True)
parent_id = db.Column(db.Integer, db.ForeignKey("researchfields.id") , nullable=True)
users = db.relationship('User', backref='researchfield', lazy='dynamic')
If I understand correctly, what you're seeking for is a way to filter out users based on a specific model. Because in your example, the other way around is redundant - every user has only one department, so no need to filter out departments for that user. In order to achieve that, I would use the backref method provided by SQLAlchemy from the User model.
Here's an example consisting of two of the models:
from sqlalchemy.orm import backref
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
password_hash = db.Column(db.String(128))
is_admin = db.Column(db.Boolean, default=False)
department_id = db.Column(db.Integer, db.ForeignKey('departments.id'))
department = db.relationship("Department", backref=backref("users", lazy="dynamic"))
class Department(db.Model):
__tablename__ = "departments"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(sqlalchemy.types.NVARCHAR(100), unique=True)
Now you can use:
department = Department.query.filter_by(id=1).first()
print(department.users.filter_by(is_admin=True).all()) # get all admins with that department
Every user has only one department, so you could just get the user's department by:
user = User.query.filter_by(id=1).first()
print(user.department) # prints Department object

How to filter by multiple criteria in Flask SQLAlchemy?

I have a database shown below which works fine. Now I have a user called Bob that owns the space Mainspace. I would like to get a boolean to see if he is a owner of the space. I tried to apply two filters but I get the following error.
sqlalchemy.exc.InvalidRequestError: Can't compare a collection to an object or collection; use contains() to test for membership.
Command:
exists = Space.query.filter_by(name="Mainspace", owner="Bob").first()
Database:
space_access = db.Table('space_access',
db.Column('userid', db.Integer, db.ForeignKey('user.id')),
db.Column('spaceid', db.Integer, db.ForeignKey('space.id')))
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(15), unique=True)
email = db.Column(db.String(50), unique=True)
password = db.Column(db.String(80))
role='admin';
spaces = db.relationship('Space', secondary=space_access, backref=db.backref('owner', lazy='dynamic'))
class Space(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), unique=True)
type = db.Column(db.String(50), unique=True)
Try this:
existing = User.query.join(User.spaces).filter(User.username=='Bob', Space.name=='Mainspace').first()
print(existing.id)
if existing != None:
print('Exists')

How do i use signals to allow many users with the same role in Flask-user

Im am using flask-user to create a webapp that allows multiple users with different roles.
in my current implementation, when a user creates an account, a role with name account_owner relating to the user is added to the database using flask signals
With my current implementation, only one user can be added with a role.
Am having trouble adding other users with the same role.
this is my signal code:
from app.models import User, Role, UserRoles
from flask_user import user_registered
#user_registered.connect_via(app)
def _after_register_hook(sender, user, **extra):
idd = user.id
rl = "account_owner"
#check if the role exists
if not Role.query.filter_by(name = rl).first():
#if not, add the user with the role.
if User.query.filter_by(id = idd).first():
updt = User.query.filter_by(id = idd).first()
updt.roles.append(Role(name = rl))
db.session.commit()
flash('role added')
return redirect(url_for('home.index'))
#(this is implemented from the second user)if the role exists, add the user to the UseRoles list for the first role.
else:
UserRoles(user_id=user.id, role_id=1)
flash('role added')
return redirect(url_for('home.index'))
and my database model is:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
# User authentication information
username = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(255), nullable=False, server_default='')
reset_password_token = db.Column(db.String(100), nullable=False, server_default='')
# User email information
email = db.Column(db.String(255), nullable=False, unique=True)
confirmed_at = db.Column(db.DateTime())
# User information
active = db.Column('is_active', db.Boolean(), nullable=False, server_default='0')
#Relationships
roles = db.relationship('Role', secondary='user_roles',backref=db.backref('users', lazy='dynamic'))
# Define the Role data model
class Role(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), unique=True)
# Define the UserRoles data model
class UserRoles(db.Model):
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('role.id', ondelete='CASCADE'))
You don't add and commit UserRole to database if the role is existed. And the logic of your code isn't clear.
from app.models import User, Role, UserRole
from flask_user import user_registered
#user_registered.connect_via(app)
def after_register_hook(sender, user, **extra):
role = Role.query.filter_by(name="account_owner").first()
if role is None: # just check if the role is existed, if not ,create it. You'd better to create the role in the initial process of the applicaiton.
role = Role(name="account_owner")
db.session.add(role)
db.session.commit()
# You needn't to check the existence of the user.
user_role = UserRole(user_id=user.id, role_id=role.id)
db.session.add(user_role)
db.session.commit()
flash("role added.")
return redirect(url_for("home.index"))

Categories