Flask SQLAlchemy query information in many to many relationship - python

I want to query the DB filtered by my order_id and have access to the information for the category_name.
I've tried reading through several answers like this one, but I can't quite get to where I want to be. I have a models such as:
models.py
product_category_table = db.Table('prouct_to_category_association',
db.Column('product_id', db.Integer, db.ForeignKey('product.id')),
db.Column('category_id', db.Integer, db.ForeignKey('category.id')))
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), index=True)
risks = db.relationship("Product",
secondary=product_category_table,
back_populates="categories")
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
order_id = db.Column(db.Integer, db.ForeignKey('Order.id'))
product_value = db.Column(db.Float(1,2), index=True)
categories = db.relationship("Category",
secondary=risk_category_table,
back_populates="risks")
My existing query looks like:
products = Product.query.filter_by(order_id = 1).all()
and when I want to access the information I am using (in Jinja2 format):
{% for product in products %}
cat = {{product.categories}}, val={{product.product_value}}
{% endfor %}
This allows me to print:
cat = [1], val = 5.99, cat = [2], val = 6.55, and so on...
However, what I can't figure out is how I can access not just the category.id, but the actual category.name... I understand why I am getting it category.id (from the association table).
Questions
How do I update my query so the return will have this information?
How do I reference it in Jinja?

you can make backref in category table
risks = db.relationship("Product",
secondary=product_category_table,
back_populates="categories",backref=db.backref('cat'))
access it using product.cat.name or product.cat.risks in jinja2
after using backref you can access all the rows of Categories

Related

How to filter a Model with a One-To-Many Relationship using Flask-Sqlalchemy?

I'm new to Flask-SQL-Alchemy so this may be a noob quesiton. Let's say I have a Tweet model:
class Tweet(db.Model):
__tablename__ = 'tweet'
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String(500), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
And a User model, which can have many tweets:
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(50), nullable=False)
last_name = db.Column(db.String(50), nullable=False)
birth_year = db.Column(db.Integer)
tweets = db.relationship('Tweet', backref='user', lazy=True)
Now, I want to apply different filters on the User table. For example, to fetch all the users born in 1970 whose first name is John
filters = (
User.first_name == 'John',
User.birth_year == 1970
)
users = User.query.filter(*filters).all()
So far, so good! But now I want to add a filter for the tweets attribute which notice is not a real database column but rather something provided by the ORM. How can I fetch Johns who have posted more than 20 tweets? I tried:
filters = (
User.first_name == 'John',
len(User.tweets) > 20
)
But this raises an Exception: object of type 'InstrumentedAttribute' has no len().
I have also tried adding a new hybrid property in the User model:
class User(db.Model):
...
#hybrid_property
def tweet_count(self):
return len(self.tweets)
filters = (
User.first_name == 'John',
User.tweet_count > 20
)
But I still get the same error. This seems like a common task but I'm not able to find any documentation or related examples. Am I approaching the problem the wrong way?
I think you are missing the second part as seen here SQLAlchemy - Writing a hybrid method for child count
from sqlalchemy.sql import select, func
class User(db.Model):
#...
#tweet_count.expression
def tweet_count(cls):
return (select([func.count(Tweet.id)]).
where(Tweet.user_id == cls.id).
label("tweet_count")
)

Flask SQLAlchemy filter by ONE to MANY

My Flask application users enter some data into form which I receive on server:
t_date = request.form['t_date'] #convert to date
t_date = request.form['t_date'] #convert to date
certificate = request.form['certificate'] #name string
skill = request.form['skill'] #name string
...
Then I query needed data with flask-sqlalchemy from MainModel like this:
query = MainModel.query.filter(
((MainModel.from_date >= f_date) &
(MainModel.from_date <= t_date)).self_group() |
((MainModel.to_date >= f_date) &
(MainModel.to_date <= t_date)).self_group()).all()
1. MainModel has MANY to ONE Company(one company can have many)
2. ONE Company has MANY Certificates and MANY Skills
Problem: User wants to enter Certificate, Skill and Dates in the form and find all records under MainModel, but Company related to that record/model must contain Certificate and/or Skill requested.
I tried: to get all records from MainModel only by looping through the mentioned query:
#case where user entered only certificate, but not skill
for qry in query: #all records between selected dates from MainModel
company = Company.query.filter_by(id=qry.company).first() #Company related to MainModel
for item in company.certificates: #Certificates belonging to that company
if item.name == certificate: #comparing user input from form and if company has such
# if all true, put row from MainModel on dict to be returned
Desired: I am not sure if looping is causing any performance issues, but it is making the the complete code a bit messy. So how can I improve the initial query to avoid too many loops? Something like:
query I wrote above + all().filter_by(MainModel.company.certificates.contains(certificate) and .skills.contains(skill)
Update as per comment request:
Models sample:
class MainModel(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
company = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=False)
class Company(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
certificates = db.relationship('Certificate',
secondary=certif_company,
backref=db.backref('company',
lazy='dynamic'))
class Certificate(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
certif_company = db.Table('certif_company',
db.Column('certificate_id', db.Integer, db.ForeignKey('certificate.id'), primary_key=True),
db.Column('company_id', db.Integer, db.ForeignKey('company.id'), primary_key=True)
)

How do I correctly create a flask sqlalchemy many to many relationship with multiple foreign keys

In the Flask sqlalchemy documentation an example of using a simple many to many relationship is given:
tags = db.Table('tags',
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id')),
db.Column('page_id', db.Integer, db.ForeignKey('page.id'))
)
class Page(db.Model):
id = db.Column(db.Integer, primary_key=True)
tags = db.relationship('Tag', secondary=tags,
backref=db.backref('pages', lazy='dynamic'))
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
Where one can use the following syntax to reach out to the related objects:
Page.tags
What I am trying to accomplish is basically to add the relationship below to the one above:
tag_children = db.Table('tag_children,',
db.Column('parent_id', db.Integer, db.ForeignKey('tags.tag_id')),
db.Column('child_id', db.Integer, db.ForeignKey('tags.tag_id'))
)
So that each page has tags attached to it, but each tag can have multiple children in the scope of that page. I've made a show case for a Page called cars below where I have it's tags and their respective children tags.
(Page) Cars:
Mercedes
A-series
B-series
C-series
Tesla
Tesla Roadster
Model X
Model S
Model 3
All the list items above are tag objects
I want to be able to use the following syntax bellow to get the related objects:
Page.tag.children
For instance (obvously dummy code below, but I want to be clear about what the intended purpose of the relationship is):
Cars.tesla.children
I think, you don't need another table for tag_children. Try to use SQLAlchemy Adjacency Lists:
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('tag.id'))
children = relationship("Tag",
backref=backref('parent', remote_side=[id])
)
With this schema you may use syntax like:
for tag in page.tags: # where page is a Page instance received from db
print tag.children
It is a common syntax for working with SQLAlchemy models. Try to use it instead of the proposed Cars.tesla.children.
Something like Cars['tesla'].children may be implemented via getitem method, but i think, it's very unclear way.
Full code snippet:
class Page(Base):
__tablename__ = 'page'
id = Column(Integer, primary_key=True)
name = Column(String(256))
tags = relationship('Tag', secondary='tags',
backref=backref('pages', lazy='dynamic'))
def __str__(self):
return self.name
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(256))
parent_id = Column(Integer, ForeignKey('tag.id'))
children = relationship(
"Tag",
backref=backref('parent', remote_side=[id])
)
def __str__(self):
return self.name
class Tags(Base):
__tablename__ = 'tags'
tag_id = Column(Integer, ForeignKey('tag.id'), primary_key=True)
page_id = Column(Integer, ForeignKey('page.id'), primary_key=True)
And test case:
# Create page and tags
session.add(Page(id=1, name="cars"))
session.add(Tag(id=1, name="Mercedes"))
session.add(Tag(id=2, name="A-series", parent_id=1))
session.add(Tag(id=3, name="B-series", parent_id=1))
session.add(Tag(id=4, name="C-series", parent_id=1))
session.add(Tag(id=5, name="Tesla"))
session.add(Tag(id=6, name="Tesla Roadster", parent_id=5))
session.add(Tag(id=7, name="Model X", parent_id=5))
session.add(Tag(id=8, name="Model S", parent_id=5))
session.add(Tag(id=9, name="Model 3", parent_id=5))
# Fill relation
session.add(Tags(tag_id=1, page_id=1))
session.add(Tags(tag_id=5, page_id=1))
session.commit()
print session.query(Page).get(1) # >>> cars
print session.query(Page).get(1).tags # >>> Mercedes, Tesla
print session.query(Page).get(1).tags[1].children # >>> Tesla models

Make a relationship between two tables though another

class Condominium(db.Model):
__tablename__ = 'condominiums'
id = db.Column(db.Integer, primary_key=True)
properties = db.relationship('Property', backref='condominiums')
class Property(db.Model):
__tablename__ = 'properties'
id = db.Column(db.Integer, primary_key=True)
condominium_id = db.Column(db.Integer, db.ForeignKey('condominiums.id'))
listings = db.relationship('Listing', backref='property')
class Listing(db.Model):
__tablename__ = 'listings'
id = db.Column(db.Integer, primary_key=True)
property_id = db.Column(db.Integer, db.ForeignKey('properties.id'))
I want to list all listings for a given condominium, like this:
SELECT listings.* FROM condominiums
INNER JOIN properties ON properties.condominium_id = condominiums.id
INNER JOIN listings ON listings.property_id = properties.id
WHERE condominiums.id = 1;
I want to be able to get a listing collection like this:
condominium = Condominium.query.get(1)
listings = condominium.listings
How can I achieve that using SQLAlchemy? Is it possible?
Assuming Condominium is 1:M to Property which is 1:M to Listing and that all foreign keys are well-defined, you can get all Listings for Condominium with id 123 thus:
session.query(Listing).join(Property).join(Condominium).filter(Condominium.id=123)

Sort by Count of Many to Many Relationship - SQLAlchemy

I am using Flask-SQLAlchemy to to query my Postgres database.
I am currently trying to query for suggestions of titles with the following query:
res = Title.query.filter(Titles.name.ilike(searchstring)).limit(20)
So far so good.
Now I would like to order the results by the number of "subscribers" each Title object has.
I am aware of the following SO question: SQLAlchemy ordering by count on a many to many relationship however its solution did not work for me.
I am receiving the following error:
ProgrammingError: (ProgrammingError) column "publishers_1.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 1: ...itles_name, count(titles_users.user_id) AS total, publishers...
(Publisher has a one-to-many relationship with Title models)
I understand that the answer has something to do with subqueries
Below is a simplified example of my two models.
# Many-to-Many relationship for user and titles
titles_users = db.Table('titles_users',
db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
db.Column('title_id', db.Integer, db.ForeignKey('titles.id'))
)
class User(db.Model, UserMixin):
__tablename__ = 'users'
# ids
id = db.Column(db.Integer, primary_key=True)
# Attributes
email = db.Column(db.String(255), unique=True)
full_name = db.Column(db.String(255))
pull_list = db.relationship(
'Title',
secondary=titles_users,
backref=db.backref('users', lazy='dynamic'),
lazy='joined'
)
class Title(db.Model):
__tablename__ = 'titles'
#: IDs
id = db.Column(db.Integer(), primary_key=True)
#: Attributes
name = db.Column(db.String(255))
Code below would work for the model you describe:
q = (db.session.query(Title, func.count(names_users.c.user_id).label("total"))
.filter(Title.name.ilike(searchstring))
.outerjoin(names_users).group_by(Title).order_by('total DESC')
)
for x in q:
print(x)
Your error, however, includes some other data like publisher or like. So if above is not helpful, you should add more information to your question.

Categories

Resources