SQLAlchemy - Right way to "double queries" - python

please what is right way for this simple model?
I have two models: Subject and Report:
class User(UserMixin, Model):
__tablename__ = 'users'
id = Column(db.Integer, primary_key=True)
email = Column(db.String(60), unique=False, nullable=True)
...
reports = db.relationship('Report', backref='user', lazy='dynamic')
class Report(UserMixin, Model):
__tablename__ = 'reports'
...
created_at = Column(db.DateTime, default=dt.datetime.now)
user_id = Column(db.Integer, db.ForeignKey('users.id'))
and i need list of all Users with date of last Report.
Dirty way is:
subjects = User.query.all()
for s in subjects:
report_date = Report.query.filter(Report.subject_id == s.id).order_by(Report.id.desc()).first()
print('Subject + Report', s.email, report_date)
Please how to create this list in clean way?
(e.g. join tables and query, temporary table with IDs of Subjects + date of last visit, store last visits date directly to table Subject ...)
Thanks.

I am not sure of what you really need but if it's to access the last report for each other of a list, you just have to do:
last_report = user.reports.order_by(Report.created_at.desc()).first()
You could had it has a property or an hybrid_property to your User model:
#property
def last_report(self):
return user.reports.order_by(Report.created_at.desc()).first()
#property
def last_report_date(self):
return self.last_report.created_at
And to get a list of tuple containing email and report_date, you could do that:
db.session.query(User.email, func.max(Product.created)).join(Report).group_by(Provider.email).all()
Anyway, if you have a lot of entries and you want to show this list to web users, you should think to use a pagination. Flask-sqlalchemy provide an easy way to do it.

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 returning object in query when working with many-to-many relationship

I'm new with SQLAlch and I'm trying to do a simple query in my database but i'm getting objects in response instead of strings. My data model is the following:
wallet_tags_association = db.Table(
'wallet_tags', db.Model.metadata,
db.Column('wallet_id', db.Integer, db.ForeignKey('wallet.id')),
db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'))
)
class WalletData(db.Model):
__tablename__ = 'wallet'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(20))
total_value = db.Column(db.DECIMAL)
tags = db.relationship('Tags', secondary='wallet_tags')
def __init__(self, name):
self.name = name
class Tags(db.Model):
__tablename__ = 'tags'
id = db.Column(db.Integer, primary_key=True)
tag_name = db.Column(db.String(20))
def __init__(self, tag_name):
self.tag_name = tag_name
and the query I'm trying to do is the following:
wallet_tags = WalletData.query.join(Tags, WalletData.tags).all()
for u in wallet_tags:
print(u.tags)
And this is what I got after iterating...
[<Tags: dividends>, <Tags: value>, <Tags: high yield>]
I have tried to follow the SQLAlch docs and there the approach is to use labels. Couldn't find a way to use labels when querying with Models.
Any help will be highly appreciated
Thanks in advance
The corrected query for this needs to be:
wallet_tags = WalletData.query.join(Tags, WalletData.tags).all()
for u in wallet_tags:
print(', '.join([tag.tag_name for tag in u.tags]))
Explanation: There is a many-to-many join between the two main tables (an intermediate table was constructed). The relationship expression on WalletData will pick up a list of connected Tags objects. Iterating through this collection for each WalletData object will yield the desired set of record field names.

Flask SqlAlchemy join two models without foreign key MYSQL

I am joining two models without a foreign key:
Models:
class Users(db.Model):
__tablename__ = "Users"
userName = db.Column(db.String, primary_key=True)
lastLogin = db.Column(db.DateTime)
class TimeOff
__tablename__ = "timeOff"
timeOffID = db.Column(db.Integer, primary_key=True)
userName = db.Column("userName", db.String, db.ForeignKey('appUsers.userName')),
dayWork = db.Column(db.DateTime)
View:
result = db.session.query(models.Users).join(models.TimeOff)
sqlalchemy.exc.InvalidRequestError: Could not find a FROM clause to join from.
Tried joining to but got: Can't find any foreign key relationships between 'TimeOff' and 'Users'.
I dont have a foreign key defined in table
You need to tell SQLAlchemy how to join the tables. Try something like this:
result = db.session.query(Users).join(TimeOff,Users.userName==TimeOff.userName)
To improve upon #Matt Healy's answer, if you also want to be able to access attributes on the joined object you can do something like:
user, timeOff = db.session.query(Users, TimeOff).join(
TimeOff, Users.userName == TimeOff.userName
).first()
Then timeOff.dayWork etc. will give the information you need.
In my situation, I want to join the two objects so that I can get the Author information of the Posts, but I don't want to use the architecture-level foreign key design to associate the two tables.here is my codes:
class Posts(db.Model):
id= db.Column(db.String, primary_key=True)
author_code= db.Column(db.String)
...
class Author:
id= db.Column(db.Integer, primary_key=True)
name= db.Column(db.String),
code= db.Column(db.String)
address= db.Column(db.String)
...
we can add the code like this:
from sqlalchemy.orm import relationship
class Posts(db.Model):
id= db.Column(db.String, primary_key=True)
author_code= db.Column(db.String)
...
# add this line
author_info = relationship('Author',
foreign_keys=[author_code],
primaryjoin='Author.code == Posts.author_code')
then we can get author_info by query like this:
post_id = xxx # some id you defined
post = Posts.query.filter_by(id=post_id).one_or_none()
# here you can get `author_info` attributes you want.
print(dir(post),post.author_info)
You can learn more about this with link here,and read the documents Configuring how Relationship Joins — SQLAlchemy 1.4 Documentation
Its an old post but I had a similar problem
result = session.query(models.Users).join(models.TimeOff, models.Users.userName == models.TimeOff.userName).all()
with this method, I can reach the features of the first object which is Users but not the TimeOff. I am wondering if it is possible to reach the secondary object's attributes. But I hope this helps.
This worked for me,
I have 3 tables, And I have the rtf_id which is unique to all three tables, you have to use the select_from keyword which tells the table starting from left.
db_data = dbobj.session.query(A, B, C). \
select_from(A).join(B, B.rtf_id == A.rtf_id). \
join(C, C.rtf_id == A.rtf_id).all()

Adding a comments field to database with flask/SQLAlchemy?

I'm working on a flask application that has a database that is set up like this and am using SQLAlchemy. (display_name was renamed to persona_name since that's what the steam api calls it.)
I'm still in the process of learning how work with databases, and what I have now works fine, I can track all the lists a user has made, and I can get suspects from all lists that one user has made. Also, removing a suspect from the suspect table also seems to remove the suspect from all lists it appears on.
This seems to work well, List and Suspect are classes that inherit from db.Model while suspect_list is an auxiliary table that is not itself a class.
The way it is set up I want to be able to have a suspect show up on multiple lists, but now I want to be able to add comments for individual suspects on individual lists.
I'm not sure how to go about this, but I have a few ideas and am not sure if they work or would have potential downsides.
1) Can I add a comment field to suspect_list?
2) Do I make a comment model as a class that inherits from db.Model and then add it to the auxiliary table instead?
3) Should I create a new id field for the suspect table, make it the primary key instead of steam_id , and then add a comment field so there can be duplicate suspects that have differing comments?
I probably could implement 3 but I don't think it is a good idea because duplicates of the same suspect probably is something that should be avoided.
As for 1 and 2 I don't know if they would work and if they did I'm not sure how to properly implement them.
This is the code that I have for my Models.py if it is needed
My question is how would I properly add comments to this database model I have set up?
Rather then an association table, you really need an association object. Here's a working example.
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.secret_key = 'MOO.'
app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite://' # In memory.
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
def __repr__(self):
return '<User {}>'.format(self.name)
class Suspect(db.Model):
__tablename__ = 'suspect'
id = db.Column(db.Integer, primary_key=True)
steam_name = db.Column(db.String)
def __repr__(self):
return '<Suspect {}>'.format(self.steam_name)
class List(db.Model):
__tablename__ = 'list'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User', backref=db.backref('lists'))
suspects = db.relationship('SuspectList', backref='list')
def __repr__(self):
return '<List {}>'.format(self.name)
class SuspectList(db.Model):
__tablename__ = 'suspect_list'
list_id = db.Column(db.Integer, db.ForeignKey('list.id'), primary_key=True)
suspect_id = db.Column(db.Integer, db.ForeignKey('suspect.id'), primary_key=True)
comment = db.Column(db.String)
suspect = db.relationship('Suspect', backref=db.backref("suspect_list"))
def __repr__(self):
return '<SL: %s %s %s>' % (self.list, self.suspect, self.comment)
if __name__ == '__main__':
with app.app_context():
db.create_all()
hacker = Suspect(steam_name='Bob')
cracker = Suspect(steam_name='Alice')
carol = User(name='Carol')
carols_list = List(name='Carols', user=carol)
hacker_suspect = SuspectList(suspect=hacker, comment='RED HAIR')
cracker_suspect = SuspectList(suspect=cracker, comment='RED EYES')
carols_list.suspects.append(hacker_suspect)
carols_list.suspects.append(cracker_suspect)
db.session.add(carols_list) # Trust SQLAlchemy to add all related.
db.session.commit()
## Querying:
my_list = List.query.filter(User.name == 'Carol').first()
for record in my_list.suspects:
print '{} reported {} with comment {}'.format(
record.list.user.name,
record.suspect.steam_name,
record.comment
)
# Carol reported Bob with comment RED HAIR
# Carol reported Alice with comment RED EYES
Two side-notes-- I found it a bit ugly to be use List as a class name, because when you want to query it, your default name would be list = List.query.fi.... which is a bit of a no-go :). Also, what program did you use to generate your ERD?
if i correctly understand, i would just create a new model related to your List Model
class Comments(db.Model):
id = db.Column(db.Integer, primary_key=True)
comment = db.Column(db.String(300))
list_id = db.Column(db.Integer, db.ForeignKey('list.id'))

Sqlalchemy - how to get an object, and filter which rows in the child class

I’m new to Sqlalchemy, and in need of some help.
i have a model, with a one to many relation:
class Metnadev(DeclarativeBase):
__tablename__ = 'metnadev'
#personal info
id = Column(Integer, autoincrement=True, primary_key=True)
first_name = Column(Unicode(255))
last_name = Column(Unicode(255))
birth_day = Column(Date)
activitys = relationship("Activity", backref="metnadev")
class Activity(DeclarativeBase):
__tablename__ = 'activity'
id = Column(Integer, autoincrement=True, primary_key=True)
metnadev_id = Column(Integer, ForeignKey('metnadev.id'))
location = Column(Unicode(255))
visible = Column(Boolean,default=True)
When I do
metnadev = DBSession.query(Metnadev).filter_by(id=kw['id']).one()
I get the object, with the child, great.
I want to get the object, but only get the rows from the child class, where visible == True
I searched but I’m not sure how to do it,
Thanks for the help
This section of the documentation has your answer: http://docs.sqlalchemy.org/en/rel_0_6/orm/relationships.html#building-query-enabled-properties
class Metnadev(DeclarativeBase):
#...
#property
def activities(self):
return object_session(self).query(Activity).filter_by(visible=True).with_parent(self).all()
There's a couple ways you can do this.
For a one-off, you can just run a second query:
from sqlalchemy import and_
activities = Activity.query.filter(and_(Activity.metnadev_id == kw['id'], Activity.visible==True)).all()
You can change the relationship so only visible items are returned:
activities = relationship("Activity",
primaryjoin="and_(Activity.metnadev_id==Metnadev.id, "
"Activity.visible==True)")
If you need more control you can join the tables, but it sounds like the relationship configuration would work for you. Let me know if that's not the case.
Hope that helps!

Categories

Resources