Select many to many using function count() - flask

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)

Related

Many to many relationship through association

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)

How to combine and match 2 tables in Flask

The id is associated with the postID. How can I get the contact information and comment information of that id when I enter the id(/postComments/id)?
I'm getting an internal error...
class posts(db.Model):
__tablename__ = "posts"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
postName = db.Column(db.String(100))
postDescription = db.Column(db.String(500))
postLike = db.Column(db.Integer)
class postComment(db.Model):
__tablename__ = "postComment"
id = db.Column(db.Integer, primary_key=True)
postID = db.Column(db.Integer)
senderName = db.Column(db.String(20))
commentPost = db.Column(db.String(300))
#app.route('/postComments/<id>',methods=['GET'])
def get_comment_post(id):
userList = posts.query\
.join(postComment, posts.id == postComment.postID)\
.add_columns(posts.id, posts.name, posts.postDescription, postComment.commentPost, postComment.senderName)\
.filter(posts.id == id)
Modify your models (and perform migrations) to allow reference for foreign keys:
class postComment(db.Model):
__tablename__ = "postComment"
id = db.Column(db.Integer, primary_key=True)
postID = db.Column(db.Integer, db.ForeignKey("posts.id")) # <--- Set to Foreign Key
senderName = db.Column(db.String(20))
commentPost = db.Column(db.String(300))
# Establish relationship
post = db.Relationship("posts", backref="postComment") # <--- backref makes this relationship available in the other class
# This should get simpler...
#app.route('/postComments/<id>',methods=['GET'])
def get_comment_post(id):
# Get the comment based on id
my_comment = postComment.query.get(id)
# Get the post associated with that comment
my_post = my_comment.post
# It looks like you're doing an intricate data transformation.
# Do that here...
# Also, for debugging, consider using the following print statements
print(my_comment.__dict__)
print(my_post.__dict__)
Consider reading this slightly more detailed explanation for establishing relationships.

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()

Flask: how to add related One-to-Many models from forms

I have two models with One-to-Many relations
first_model
class Datacenter(db.Model):
id = db.Column(db.Integer, primary_key=True)
number = db.Column(db.Integer)
name = db.Column(db.String(64), index=True, unique=True)
place = db.Column(db.String(64))
capacity = db.Column(db.Integer, index=True)
server = db.relationship('Server', backref='datacenter', lazy=True)
tier = db.Column(db.Integer)
def __repr__(self):
return '<Datacenter {}>'.format(self.name)
second_models
class Server(db.Model):
id = db.Column(db.Integer, primary_key=True)
number = db.Column(db.Integer)
name = db.Column(db.String(64), index=True, unique=True)
factory = db.Column(db.String(64), index=True)
model = db.Column(db.String(64))
serial = db.Column(db.Integer)
os = db.Column(db.String(64))
datacener_id=db.Column(db.Integer, db.ForeignKey('datacenter.id'), nullable=False)
def __repr__(self):
return '<Server {}>'.format(self.name)
However, I'm not sure that I correctly described the relationship between them in the fields of the model.
All I need is to create a new Server model with a connection to the Datacenter model.
I think that this can be done using the select field in the form. so I created a form
class ServerForm(FlaskForm):
number = IntegerField('Number')
name = StringField('Name')
factory = StringField('Factory')
model = StringField('Model')
serial = IntegerField('Model')
os = StringField('OS')
datacener_id = SelectField('Datacener', choices=[ ???? ])
and router
#app.route('/add_server', methods = ['POST', 'GET'])
def add_server():
form = ServerForm(request.form)
if request.method=='POST' and form.validate():
data = Server(
number=form.number.data,
name=form.name.data,
factory=form.factory.data,
model=form.model.data,
serial=form.serial.data,
os=form.os.data,
datacener_id=form.datacener_id.data
)
db.session.add(data)
db.session.commit()
flash('Server created successfully!')
return redirect('/')
return render_template('new_server.html', form=form)
But when I go to the page for adding a new model, the drop-down list is empty. Tell me what I need to change in my code

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')
)