Many to many relationship through association - flask

I have the following models:
Group:
class Group(db.Model):
__tablename__ = 'Groups'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), index=True, unique=True)
description = db.Column(db.Text, nullable=False)
created_on = db.Column(db.DateTime, default=datetime.now)
updated_on = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
users = db.relationship(
"User",
secondary=users_groups_assocation_table,
back_populates="groups")
analysis = db.relationship(
"Analysis",
secondary=analysis_groups_assocation_table,
back_populates="groups"
User:
class User(db.Model):
__tablename__ = 'Users'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
alias = db.Column(db.String(120), index=True, unique=True)
name = db.Column(db.String(120), index=True, unique=False)
lastname = db.Column(db.String(120), index=True, nullable=False)
email = db.Column(db.String(120), index=True, nullable=False)
password = db.Column(db.String(120), index=False, nullable=True)
created_on = db.Column(db.DateTime, default=datetime.now)
updated_on = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
admin = db.Column(db.Boolean, default=False)
local_auth = db.Column(db.Boolean, default=False)
override_tableau = db.Column(db.Boolean, default=False)
groups = db.relationship(
"Group",
secondary=users_groups_assocation_table,
back_populates="users")
Analysis:
class Analysis(db.Model):
__tablename__ = 'Analysis'
__table_args__ = {'extend_existing': True}
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), index=True, unique=True)
description = db.Column(db.Text, nullable=False)
embed_analysis = db.Column(db.Text, nullable=True)
service_account = db.Column(db.Text, nullable=True)
img = db.Column(db.LargeBinary(length=(2**32)-1), nullable=True)
img_mimetype = db.Column(db.Text, nullable=True)
img_name = db.Column(db.Text, nullable=True)
category_id = db.Column(db.Integer, db.ForeignKey('Categories.id'), nullable=True)
created_on = db.Column(db.DateTime, default=datetime.now)
updated_on = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
category = db.relationship("Category", back_populates="analysis")
draft = db.Column(db.Boolean, default=False)
groups = db.relationship(
"Group",
secondary=analysis_groups_assocation_table,
back_populates="analysis")
Association tables:
users_groups_assocation_table = db.Table('users_groups',
db.Column('user_id', db.Integer, db.ForeignKey('Users.id')),
db.Column('group_id', db.Integer, db.ForeignKey('Groups.id'))
)
analysis_groups_assocation_table = db.Table('analysis_groups',
db.Column('analysis_id', db.Integer, db.ForeignKey('Analysis.id')),
db.Column('group_id', db.Integer, db.ForeignKey('Groups.id'))
)
So there is an implicimit many to many relationship between Users <-> Analysis. How do I create such an association through groups? Coming from ruby on rails there is a :through keyword in this case, is there anything similar for flask sqlalchemy?
I want to have something like User.query.first().analysis

Many things are possible with sqlalchemy's relationships but I find that complex ones can be hard to get right/predictable and that one-off queries are easier to maintain (at least for complex relationships).
I attempted to solve the relationship you asked about, as User.analyses but included a one off query at the end.
I used a simplified version of joining across multiple tables in the docs here: https://docs.sqlalchemy.org/en/14/orm/join_conditions.html#composite-secondary-join
Note that the join uses a mix of Table objects that require columns referenced of .c. and mapped classes like Analysis that have columns directly referenced.
You also probably would want an order_by argument to relationship otherwise this relation would probably be meaningless.
from datetime import datetime, date
from sqlalchemy import (
create_engine,
Text,
Integer,
String,
ForeignKey,
UniqueConstraint,
update,
DateTime,
Date,
Boolean,
LargeBinary,
)
from sqlalchemy.schema import (
Table,
Column,
MetaData,
)
from sqlalchemy.sql import select
from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy.orm import Session
from sqlalchemy.exc import IntegrityError
Base = declarative_base()
engine = create_engine("sqlite://", echo=False)
users_groups_table = Table('users_groups', Base.metadata,
Column('user_id', Integer, ForeignKey('Users.id')),
Column('group_id', Integer, ForeignKey('Groups.id'))
)
analysis_groups_table = Table('analysis_groups', Base.metadata,
Column('analysis_id', Integer, ForeignKey('Analysis.id')),
Column('group_id', Integer, ForeignKey('Groups.id'))
)
class Group(Base):
__tablename__ = 'Groups'
__table_args__ = {'extend_existing': True}
id = Column(Integer, primary_key=True)
name = Column(String(120), index=True, unique=True)
users = relationship(
"User",
secondary=users_groups_table,
back_populates="groups")
analysis = relationship(
"Analysis",
secondary=analysis_groups_table,
back_populates="groups")
class User(Base):
__tablename__ = 'Users'
__table_args__ = {'extend_existing': True}
id = Column(Integer, primary_key=True)
name = Column(String(120), index=True, unique=False)
groups = relationship(
"Group",
secondary=users_groups_table,
back_populates="users")
analyses = relationship("Analysis",
# The middle
secondary="join(users_groups, analysis_groups, users_groups.c.group_id == analysis_groups.c.group_id)",
# Join from left to the middle
primaryjoin="User.id == users_groups.c.user_id",
# Join from right to the middle
secondaryjoin="Analysis.id == analysis_groups.c.analysis_id",
uselist=True,
viewonly=True
)
class Analysis(Base):
__tablename__ = 'Analysis'
__table_args__ = {'extend_existing': True}
id = Column(Integer, primary_key=True)
name = Column(String(120), index=True, unique=True)
groups = relationship(
"Group",
secondary=analysis_groups_table,
back_populates="analysis")
Base.metadata.create_all(engine)
with Session(engine) as session:
users = {}
for name in ('a', 'b'):
users[name] = User(name=name)
session.add(users[name])
groups = {}
for name in ('a', 'b'):
groups[name] = Group(name=name)
session.add(groups[name])
analyses = {}
for name in ('x', 'y', 'z'):
analyses[name] = Analysis(name=name)
session.add(analyses[name])
groups['a'].users.append(users['a'])
groups['b'].users.append(users['b'])
analyses['x'].groups.append(groups['a'])
analyses['y'].groups.append(groups['b'])
analyses['z'].groups.append(groups['b'])
session.commit()
print (users['a'].analyses[0].name)
# One-off ad-hoc query.
q = session.query(Analysis).join(Analysis.groups).join(Group.users).filter(User.id == users['a'].id)
print (q)
res = q.first()
print (res.name)

Related

Not able to get joins in flask Marshmellow and sql alchemy

I want to get the producttype with admin info who added the producttype I am using sqlalchemy with postgresql and marshmallow
This is my Model related info related to Admin
class Admin(db.Model):
id = db.Column(db.Integer, primary_key=True)
full_name = db.Column(db.String())
email = db.Column(db.String(), unique=True)
mobile = db.Column(db.String(), unique=True)
password = db.Column(db.String())
product_types: db.relationship("ProductType", backref="admin", lazy=True)
created_at = db.Column(DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(DateTime(timezone=True), onupdate=func.now())
def __repr__(self):
return "<Admin %r>" % self.full_name
class AdminSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Admin
admin_schema = AdminSchema()
admins_schema = AdminSchema(many=True)
This is my Model related info related to Product_Type
class ProductType(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
descripton = db.Column(db.String())
added_by_id = db.Column(db.Integer, db.ForeignKey("added_by.id"))
added_by: db.relationship("Admin", backref=db.backref("admin", lazy="dynamic"))
created_at = db.Column(DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(DateTime(timezone=True), onupdate=func.now())
def __repr__(self):
return "<ProductType %r>" % self.name
class ProductTypeSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = ProductType
added_by = ma.Nested(AdminSchema)
product_type_schema = ProductTypeSchema()
product_types_schema = ProductTypeSchema(many=True)
If anyone can suggest something please do
I want to get the producttype with admin info who added the producttype I am using sqlalchemy with postgresql and marshmallow.
The output I am expecting
product_type:{
"created_at": "2023-01-21T07:55:14.773346+05:30",
"descripton": "Product Type",
"id": 6,
"name": "dgd",
"updated_at": null,
"admin":{
"full_name":""
"email":""
"mobile":""
}
}
Unfortunately, your code contains syntax errors as well as an inaccurate definition of the database relationships. For this reason it is difficult to interpret exactly what your database looks like or which renaming the schemas must contain.
As a note on defining relationships:
The referencing via ForeignKey expects the names of the table and the primary key column separated by a period.
The backref parameter defines the name of the attribute under which the back reference is accessible.
The following example shows a possible implementation to achieve the output you are aiming for.
class Admin(db.Model):
id = db.Column(db.Integer, primary_key=True)
full_name = db.Column(db.String())
email = db.Column(db.String(), unique=True)
mobile = db.Column(db.String(), unique=True)
password = db.Column(db.String())
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now())
class AdminSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Admin
exclude = ('password',)
class ProductType(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
description = db.Column(db.String())
admin_id = db.Column(db.Integer, db.ForeignKey("admin.id"))
admin = db.relationship("Admin", backref=db.backref("product_types", lazy="dynamic"))
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_at = db.Column(db.DateTime(timezone=True), onupdate=func.now())
class ProductTypeSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = ProductType
admin = ma.Nested(AdminSchema(only=('full_name','mobile', 'email')))

Can't get Association table to eager load with Movie for particlular User, to show if the User who added the movie is being followed or not Flask

class Movie(db.Model):
__searchable__ = ['genre']
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
username = db.Column(db.String(255))
description = db.Column(db.String(100))
class User(db.Model,UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String, nullable=False)
password = db.Column(db.String, nullable=False)
movies = db.relationship('Movie', backref='author', lazy='joined')
followed = db.relationship('User', secondary=followers,
primaryjoin=(followers.c.follower_id==id),
secondaryjoin=(followers.c.followed_id==id),
backref=db.backref('followers', lazy='joined'), lazy='joined')
followers = db.Table('followers',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id'), primary_key = True),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'), primary_key = True) )
def get_all_movies_follow():
qry = Movie.query.order_by(Movie.id.desc()).all()
movies_schema = MovieSchema(many=True)
return movies_schema.dump(qry)
How to get back an collection (array) where I have a property that shows whether
When I eager_load followers table it's still won't show up in the query.
``` #movi = Movie.query.outerjoin(followers, followers.c.followed_id == Movie.user_id).options(contains_eager( Movie.us).contains_eager( 'followers')).order_by(Movie.id.desc()).all()
Also when I try to use follow unfollow function I get Select statement 'SELECT *
FROM followers, movie AS movie_1
WHERE followers.followed_id = movie_1.user_id' returned no FROM clauses due to auto-correlation; specify correlate(<tables>) to control correlation manually.
which in short is def unfollow(id):
...
current_user.unfollow(user)
db.session.commit()
def follow(self, user):
if not self.is_following(user):
self.followed.append(user)
def unfollow(self, user):
if self.is_following(user):
self.followed.remove(user)
def is_following(self, user):
return self.query.filter(followers.c.followed_id==user.id).count()>0
So I tried adding this to Movie class: ```following = column_property( exists().where(followers.c.followed_id==user_id)) but it has to be also restricted on current_user.id=followers.c.follower_id
I am thinking maybe statement that will be included when I query for the Movie
ok, so this works, granted that I included following in the schema to dump on MovieSchema.
But it has a problem. If there are no matches to (followers.c.follower_id) so user is not following anyone. then I get an empty result and no movie gets loaded at all.
class Movie(db.Model)
...
following = column_property( exists().where(followers.c.followed_id==user_id).correlate_except(followers))
and in query
qry = db.session.query(Movie).filter(followers.c.follower_id == current_user.id).all()

How to add relationship instances of a model on sqlalchemy's before_insert event?

I want to add instances of a model's relationship when an instance of this model is created.
While before_insert sqlalchemy event allows to set simple attribute, it does not seem to work with relationship.
Is it possible to do that with this event ? What is the standard way to achieve that ?
audio_project_rel = db.Table(
'audio_project_rel',
db.Column('project_id', db.Integer, db.ForeignKey('project.id'), primary_key=True),
db.Column('audio_id', db.Integer, db.ForeignKey('audio.id'), primary_key=True)
)
class Audio(db.Model):
id = db.Column(db.Integer, primary_key=True)
path = db.Column(db.String, unique=True, nullable=False)
class Project(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True, nullable=False)
audiolist_filename = db.Column(db.String, nullable=False)
audios = db.relationship('Audio',
secondary=audio_project_rel,
lazy=True,
backref=db.backref('projects', lazy=True))
#event.listens_for(Project, 'before_insert')
def get_audiolist_from_file(mapper, connection, project):
with open(project.audiolist_filename, 'r') as audiolist_file:
for line in audiolist_file:
_path = line.strip()
audio = Audio.query.filter(Audio.path==_path).first()
if not audio:
audio = Audio()
audio.path = _path
project.audios.append(audio) # not added
project.name = 'somename' # added
It seems that it is not possible:
https://docs.sqlalchemy.org/en/latest/orm/session_events.html#session-persistence-mapper

Select many to many using function count()

I have many to many relationships and i try to find User which has a minimum requests im my subs table but i can't understand how i can do it.
Could you please clarify how i can do it
my Models are:
subs = db.Table('subs',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('request_id', db.Integer, db.ForeignKey('request.id'))
)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120))
role = db.Column(db.String(120))
password_hash = db.Column(db.String(120))
requests = db.relationship('Request', secondary=subs,
backref=db.backref('users', lazy='dynamic'))
post = db.relationship('Posts', backref = 'user', lazy = 'dynamic')
request = db.relationship('Request', backref='user', lazy = 'dynamic')
is_active = db.Column(db.String(120))
class Request(db.Model):
__tablename__ = 'request'
id = db.Column(db.Integer, primary_key=True)
org = db.Column(db.String(120))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
cost = db.Column(db.Integer)
created = db.Column(db.DateTime, default= datetime.utcnow)
cost_time = db.Column(db.Integer)
update_time = db.Column(db.DateTime, default = datetime.utcnow())
diff_time = db.Column(db.DateTime)
feedback = db.Column(db.Text, default=update_time)
comment = db.relationship('Posts', backref = 'request', lazy='dynamic')
rate_idea = db.Column(db.Integer)
new = db.Column(db.Text)
cost_buyer = db.relationship('Costs', backref = 'request', lazy='dynamic')
status = db.Column(db.String(120), db.ForeignKey('status.id'))
For example:
User1.requests = [Request_1, 'Request_2, Request_3]
User2.requests = [Request_2, Request_3]
When somebody do a new Request i need to clarify firstly which user has a minimum requests from all of users and then put this request to him.
New_request = Request(org = 'TEST')
In this case User2 must add this New_request to his own User.requests so the final result must be
User1.requests = [Request_1, 'Request_2, Request_3]
User2.requests = [Request_2, Request_3, New_request]
i want to do query something like this, but what is the right and simple solution for this i don't know and i want to know:
db.query.filter(min(len(User.requests))
Something like this should work. But I suggest you to check the docs.
from sqlalchemy import func
db.query(User.id, func.count())
.outerjoin(User.requests)
.group_by(User.id)
.order_by(func.count())
.limit(1)

Selecting an item by its associated tables

I'm working on building a recipe database. I'm trying to build a query wehere I get all recipies that include a certain ingredient (such as onions, carrots), but I'm not how build my query. Essentally I'm trying to get a list of recipies that (given the proper amount of joins) have an Ingredient.name = 'onion'. My models are as follows:
ingredients = db.Table('ingredients',
db.Column('modified_ingredient', db.Integer, db.ForeignKey('modified_ingredient.id')),
db.Column('ingredient', db.Integer, db.ForeignKey('ingredient.id'))
)
modifiers = db.Table('modifiers',
db.Column('modified_ingredient', db.Integer, db.ForeignKey('modified_ingredient.id')),
db.Column('modifier', db.Integer, db.ForeignKey('modifier.id'))
)
modified_ingredients = db.Table('modified_ingredients',
db.Column('recipe', db.Integer, db.ForeignKey('recipe.id')),
db.Column('modified_ingredient', db.Integer, db.ForeignKey('modified_ingredient.id'))
)
class Recipe(db.Model):
__tablename__ = 'recipe'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(256))
description = db.Column(db.Text)
directions = db.Column(db.Text)
prep_time = db.Column(db.Integer)
cook_time = db.Column(db.Integer)
image = db.Column(db.LargeBinary())
ingredients = db.relationship('ModifiedIngredient', secondary=modified_ingredients)
class Ingredient(db.Model):
__tablename__ = 'ingredient'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), index=True, unique=True)
class Modifier(db.Model):
__tablename__ = 'modifier'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), index=True, unique=True)
class ModifiedIngredient(db.Model):
__tablename__ = 'modified_ingredient'
id = db.Column(db.Integer, primary_key=True)
amount = db.Column(db.Integer)
unit = db.Column(db.String(20))
ingredients = db.relationship('Ingredient', secondary=ingredients,
backref=db.backref('ingredients', lazy='dynamic'), lazy='dynamic')
modifiers = db.relationship('Modifier', secondary=modifiers,
backref=db.backref('modifiers', lazy='dynamic'), lazy='dynamic')
It's mostly my inexperience with SQL and SQLAlchemy that is stumping me. I know that I'm joining something, but I'm not exactly sure how to phrase it in a way that works.
Option-1: very tidy, but might not be the most efficient due to nested EXISTS clause:
q = (db.session.query(Recipe)
.filter(Recipe.ingredients.any(
ModifiedIngredient.ingredients.any(
Ingredient.name == 'onion')
)))
Option-2: should be faster, but if you query only certain columns (use query(Recipe.name, ..) instead of whole objects as below), you will end with with multiple results per each Recipe row because of JOINs:
q = (db.session.query(Recipe)
.join(Recipe.ingredients)
.join(Ingredient, ModifiedIngredient.ingredients)
.filter(Ingredient.name == 'onion')
)