How to query two association tables in Flask-SQLAlchemy? - python

I have two tables: courses and students, and two association tables: registered_students and attended_students. I am trying to query the two association tables to find the students who registered and attended a course. I found answers to how to query one association table, but I have two. I'm quite new to SQLAlchemy, so the complexity of it is beyond me.
Here are my models:
class Course(db.Model):
__tablename__ = 'courses'
__table_args__ = (db.UniqueConstraint('topic', 'date', name='topic_date_unique'),)
id = db.Column(db.Integer, primary_key=True)
topic = db.Column(db.String(64), nullable=False)
date = db.Column(db.DateTime, nullable=False)
registered_students = db.relationship('Student', secondary=registrations, lazy='dynamic',
backref=db.backref('registered_course', lazy='dynamic'))
attended_students = db.relationship('Student', secondary=attendances, lazy='dynamic',
backref=db.backref('attended_course', lazy='dynamic'))
class Student(db.Model):
__tablename__ = 'students'
__table_args__ = (db.UniqueConstraint('email', 'phone', name='email_phone_unique'), )
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), nullable=False)
email = db.Column(db.String(64), nullable=False)
phone = db.Column(db.Integer, nullable=False)
registrations = db.Table('registrations',
db.Column('course_id', db.Integer, db.ForeignKey('courses.id'), primary_key=True),
db.Column('student_id', db.Integer, db.ForeignKey('students.id'), primary_key=True)
)
attendances = db.Table('attendances',
db.Column('course_id', db.Integer, db.ForeignKey('courses.id'), primary_key=True),
db.Column('student_id', db.Integer, db.ForeignKey('students.id'), primary_key=True)
)

In the end, I decided to go with a no ORM solution. Maybe it's not the best, but it works for me. If you know how to do it in Flask-SQLAlchemy, please share.
def get_registered_and_attended_students_for(course_id):
connection = sqlite3.connect(DATABASE)
cursor = connection.cursor()
return cursor.execute(f"""SELECT registrations.student_id FROM registrations INNER JOIN attendances ON registrations.course_id = attendances.course_id
WHERE registrations.student_id = attendances.student_id AND attendances.course_id = {course_id}""").fetchall()

Related

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

SqlAlchemy Error on Creating multiple foreign key to one table

I am new using sqlAlchemy and having problem creating new tables, specially when it comes around 2 foreign keys pointing to 1 table:
class Offers(db.Model):
__tablename__ = 'offers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
contact_ign = db.Column(db.String(100))
conversion_rate = db.Column(db.Float)
stock = db.Column(db.Integer)
create_date = db.Column(db.DateTime(timezone=True), default=func.now())
currency_pair = db.relationship('CurrencyPairs', backref='pair', lazy='dynamic')
class CurrencyPairs(db.Model):
__tablename__ = 'currency_pairs'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
league = db.Column(db.String(100))
pair_id = db.Column(db.Integer, db.ForeignKey('offers.id'))
want = db.relationship('Currency', backref='want', lazy='dynamic')
have = db.relationship('Currency', backref='have', lazy='dynamic')
class Currency(db.Model):
__tablename__ = 'currency'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
poe_trade = db.Column(db.Integer, nullable=False)
poe_official = db.Column(db.String(10), nullable=False)
tier = db.Column(db.Integer, nullable=False)
want_id = db.Column(db.Integer, db.ForeignKey('currency_pairs.id'))
have_id = db.Column(db.Integer, db.ForeignKey('currency_pairs.id'))
The error I am getting is:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper|CurrencyPairs|currency_pairs'. Original exception was: Could not determine join condition b
etween parent/child tables on relationship CurrencyPairs.want - there are multiple foreign key paths linking the tables. Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key refe
rence to the parent table
I have try different things but I get same result.
What am I doing wrong?
Thanks In advance.
I know this is an old question, but I had the same problem. I hope to help others with the answer.
This issue is addressed in the sqlalchemy documentation.
https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths
class Offers(db.Model):
__tablename__ = 'offers'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
contact_ign = db.Column(db.String(100))
conversion_rate = db.Column(db.Float)
stock = db.Column(db.Integer)
create_date = db.Column(db.DateTime(timezone=True), default=func.now())
currency_pair = db.relationship('CurrencyPairs', backref='pair', lazy='dynamic')
class CurrencyPairs(db.Model):
__tablename__ = 'currency_pairs'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
league = db.Column(db.String(100))
pair_id = db.Column(db.Integer, db.ForeignKey('offers.id'))
want_currency = relationship("Currency", foreign_keys='[Currency.want_id]', back_populates="want_currency_pairs")
have_currency = relationship("Currency", foreign_keys='[Currency.have_id]', back_populates="have_currency_pairs")
class Currency(db.Model):
__tablename__ = 'currency'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
poe_trade = db.Column(db.Integer, nullable=False)
poe_official = db.Column(db.String(10), nullable=False)
tier = db.Column(db.Integer, nullable=False)
want_currency_pairs = relationship(CurrencyPairs, foreign_keys="[Currency.want_id]", back_populates="want_currency")
have_currency_pairs = relationship(CurrencyPairs, foreign_keys="[Currency.have_id]", back_populates="have_currency")
The way you wrote the code, sqlalchemy can't really understand which relationship to choose, because you have 2 of the same relationship. So you have to describe to sqlalchemy that there are 2 relationships to the same table.

SQLAlchemy count function for nested join subquery

I'm very new at SQLAlchemy and as you can see I have 3 models:
categories = db.Table(
'categories',
db.Column('post_id', db.Integer, db.ForeignKey('post.id')),
db.Column('category_id', db.Integer, db.ForeignKey('category.id'))
)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
posts = db.relationship('Post', backref='author', lazy='dynamic')
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
categories = db.relationship("Category",
secondary="categories",
backref="posts")
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
I can count how many post each user has by some subquery like this:
stmt = db.session.query(models.Post.user_id, func.count('*').label('post_count')).group_by(models.Post.user_id).subquery()
db.session.query(models.User, stmt.c.post_count).outerjoin(stmt, models.User.id==stmt.c.user_id).order_by(models.User.id)
but I can't figure it out the right query to calculate how many categories each users has.
You could for example add a join to categories in your subquery and count distinct Post.ids and category_ids grouped by user_id:
stmt = db.session.query(
models.Post.user_id,
func.count(models.Post.id.distinct()).
label('post_count'),
func.count(models.categories.c.category_id.distinct()).
label('category_count')
).\
join(models.categories).\
group_by(models.Post.user_id).\
subquery()
db.session.query(models.User,
stmt.c.post_count,
stmt.c.category_count).\
outerjoin(stmt, models.User.id==stmt.c.user_id).\
order_by(models.User.id)
The post id's have to be distinct as well in case a post has multiple categories, which will manifest as multiple rows for the post after the join.

many to many relationship with three tables (sql-alchemy)

I am trying to get a many to many relationship working. I have three tables
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
class Groups(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(1000))
class Members(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
group_id = db.Column(db.Integer, db.ForeignKey('groups.id'))
I would like to have the option group.members, which should give me all User objects which are member of that group. I implemented it the following way
members = db.relationship('User', secondary="join(Members, User, Members.user_id == User.id)", primaryjoin="and_(Groups.id == Members.group_id)")
this seems to work, but when I delete a group it gives me (sometimes) the error
AttributeError: 'Join' object has no attribute 'delete'
so I guess this is not the right way to implement such a relation.
Any ideas how to do this correctly?
thanks
carl
Perhaps a simpler way to implement this is as follows (adapted from the documentation on Flask-SQLAlchemy
members = db.Table('members',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('group_id', db.Integer, db.ForeignKey('groups.id'))
)
class Groups(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(1000))
members = db.relationship('User', secondary=members, backref=db.backref('group', lazy='dynamic'))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
Instead of using a Model for the joining table (members), let's just use a simple table.
With this configuation, you can easily add/remove members and groups:
u = User(username='matt')
g = Groups(name='test')
db.session.add(u)
db.session.add(g)
db.session.commit()
g.members.append(u)
db.session.commit()
db.session.delete(g)
db.session.commit()

Query many-to-many in SQLAlchemy

I have the following models. A user has many roles, and a role can have many permissions. I can't quite figure out how to query to get what I want.
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'))
I want to get a (preferably unique) list of all the permissions assigned to a user, but I can't seem to figure out how to do it.
I can get the list by creating a generator in the User model:
def get_all_permissions(self):
for role in self.roles:
for perm in role.permissions:
yield perm
But I'd love to be able to do it in one query.
Well, to just get a list of the permissions, try something like this:
permissions = session.query(Permission).\
join(Role).join(User).filter(User.username='MisterX').all()
Or filter for whatever you want. To make the Permissions unique, you could use group by:
permissions = session.query(Permission.id, Permission.name).join(Role).join(User).\
filter(User.username='MisterX').group_by(Permission.id).all()
Or, without a special query, use the declarative extension, if you can:
permissions = User.roles.permissions
Does that help?
Possibly your association tables are not recognized properly, as you did not specify the metadata parameter. This script works for me:
#!/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)
If you have loaded User object already into memory together with Permissions and Roles, you code should do it quickly and without going to a database.
Otherwise, this query should work:
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)

Categories