I'm using Flask to build a RESTful api and use SQLAlchemy to connect my app to a MySQL database.
I have two models in databse : Order and Order_line. An order is made of several order lines. Each order lines has a status associated.
I'm having trouble translating my SQL request into a Flask-SQLAlchemy statement. I'm especially bugged by the join.
Here are my models:
class Order(db.Model):
id = db.Column(db.Integer, primary_key=True)
date_created = db.Column(db.DateTime)
lines = db.relationship('Order_line',
backref=db.backref('order',
lazy='join'))
def __init__(self, po):
self.date_created = datetime.datetime.now()
class Order_line(db.Model):
id = db.Column(db.Integer, primary_key=True)
order_id = db.Column(db.Integer, db.ForeignKey('order.id'))
status_id = db.Column(db.Integer, db.ForeignKey('status.id'))
def __init__(self, order_id):
self.order_id = order_id
self.status_id = 1
class Status(db.Model):
id = db.Column(db.Integer, primary_key=True)
short_name = db.Column(db.String(60))
description = db.Column(db.String(400))
lines = db.relationship('Order_line',
backref=db.backref('status',
lazy='join'))
def __init__(self, short_name, description):
self.short_name = short_name
self.description = description
Basically, I want to retrieve all the orders (so retrieve the Order.id) which have one or more order_line's status_id different from 1.
The SQL query would be
SELECT id FROM `order`
INNER JOIN order_line
ON order.id=order_line.order_id
WHERE
order_line.status_id NOT LIKE 1
GROUP BY
order.id
I didn't find a way to translate that SQL statement into a SQLAlchemy command. I'm especially confused by the difference between Flask-SQLAlchemy wrapper and 'vanilla' SQLAlchemy.
You can use .any():
Order.query.filter(Order.lines.any(Order_line.status_id != 1))
I'm also new in Flask-SQLAlchemy but I've been learning a lot lately for my app. Then, some point that could help you:
The main difference between 'vanilla' and Flask-SQLAlchemy at the moment to do a query, it is the way Flas-SQLAlchemy handle the session variables. In the Flask version you have a db object that handle your session as in this case:
db = SQLAlchemy()
With that object, you handle the query. In your case, your query could be performed in this way:
db.session.query(Order).filter(Order.id==Order_line.order_id).filter(Order_line.status_id!=1).group_by(Order.id).all()
This is not exactly the same query but it quite similar. It will return you all the fields from the Order table but if you want only the "id", you can change "Order" for "Query.id" in the query statement. The "like" filter that you have I'm not totally sure how to implement it in Flask-SQLAlchemy but I found this question that perform an answers for the "vanilla" SQLalchemy: how to pass a not like operator in a sqlalchemy ORM query
Can also use
db.session.query(Order.id).filter(Order.lines.status_id != 1 ).group_by(Order.id).all()
Related
Imagine I've got the following:
class User:
id = Column(Integer, primary_key=True)
username = Column(String(20), nullable=False)
password_hash = Column(String(HASH_LENGTH), nullable=False)
class LoginAttempts:
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey(User.id))
attempted_at = Column(DateTime, default=datetime.datetime.utcnow)
Now, I want to add a relationship to User called last_attempt that retrieves the most recent login attempt. How might one do this?
This seems like a use case for a relationship to an aliased class, which was added in SQLAlchemy 1.3 – before that you'd use a non primary mapper, or other methods such as a custom primary join. The idea is to create a subquery representing a derived table of latest login attempts per user that is then aliased to LoginAttempts and used as the target of a relationship. The exact query used to derive the latest attempts depends on your DBMS1, but a generic left join "antijoin" will work in most. Start by generating the (sub)query for latest login attempts:
newer_attempts = aliased(LoginAttempts)
# This reads as "find login attempts for which no newer attempt with larger
# attempted_at exists". The same could be achieved using NOT EXISTS as well.
latest_login_attempts_query = select([LoginAttempts]).\
select_from(
outerjoin(LoginAttempts, newer_attempts,
and_(newer_attempts.user_id == LoginAttempts.user_id,
newer_attempts.attempted_at > LoginAttempts.attempted_at))).\
where(newer_attempts.id == None).\
alias()
latest_login_attempts = aliased(LoginAttempts, latest_login_attempts_query)
Then just add the relationship attribute to your User model:
User.last_attempt = relationship(latest_login_attempts, uselist=False,
viewonly=True)
1: For example in Postgresql you could replace the LEFT JOIN subquery with a LATERAL subquery, NOT EXISTS, a query using window functions, or SELECT DISTINCT ON (user_id) ... ORDER BY (user_id, attempted_at DESC).
Although the selected answer is more robust, another way you could accomplish this is to use a lazy=dynamic and order_by:
User.last_attempted = relationship(LoginAttempts, order_by=desc(LoginAttempts.attempted_at), lazy='dynamic')
Be careful though, because this returns a query object (and will require .first() or equivalent), and you will need to use a limit clause:
last_attempted_login = session.query(User).get(my_user_id).last_attempted.limit(1).first()
I am using Flask-SQLAlchemy to define my models, and then using Flask-Migrate to auto-generate migration scripts for deployment onto a PostgreSQL database. I have defined a number of SQL Views on the database that I use in my application like below.
However, Flask-Migrate now generates a migration file for the view as it thinks it's a table. How do I correctly get Flask-Migrate / Alembic to ignore the view during autogenerate?
SQL View name: vw_SampleView with two columns: id and rowcount.
class ViewSampleView(db.Model):
__tablename__ = 'vw_report_high_level_count'
info = dict(is_view=True)
id = db.Column(db.String(), primary_key=True)
rowcount = db.Column(db.Integer(), nullable=False)
Which means I can now do queries like so:
ViewSampleView.query.all()
I tried following instructions on http://alembic.zzzcomputing.com/en/latest/cookbook.html and added the info = dict(is_view=True) portion to my model and the following bits to my env.py file, but don't know where to go from here.
def include_object(object, name, type_, reflected, compare_to):
"""
Exclude views from Alembic's consideration.
"""
return not object.info.get('is_view', False)
...
context.configure(url=url,include_object = include_object)
I think (though haven't tested) that you can mark your Table as a view with the __table_args__ attribute:
class ViewSampleView(db.Model):
__tablename__ = 'vw_report_high_level_count'
__table_args__ = {'info': dict(is_view=True)}
id = db.Column(db.String(), primary_key=True)
rowcount = db.Column(db.Integer(), nullable=False)
I am having some difficulty setting up a one to one relationship between two models in my flask application. I have two models Employeeand `Photo'. An employee has only one photo associated with it and vice-versa.
This is the code that I have in my models.py file:
class Employee(db.Model):
__tablename__ = 'employees'
id = db.Column(db.Integer, primary_key=True)
photo = db.relationship("Photo", uselist=False, back_populates='employees')
class Photo(db.Model):
__tablename__ = 'photos'
id = db.Column(db.Integer, primary_key=True)
employee_id = db.Column(db.Integer, db.ForeignKey('employees.id'))
employee = db.relationship('Photo', back_populates='photo')
I've followed the instruction on the SQL Alchemy documentation found hereSQL Alchemy simple relationships. The error that I keep encountering is shown below:
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Photo.employee
- there are no foreign keys linking these tables.
Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
I clearly specify the foreign key right here employee_id = db.Column(db.Integer, db.ForeignKey('employees.id')) . I'm not sure what I'm doing wrong. Additionally, I was reading the documentation and it doesn't help that uselist, backref, and back_populates are so similar.
Can someone assist me with this? Help would be greatly appreciated.
One to One relationship stack overflow question
backref automatically adds the reverse relationship to the related model. You can pass a db.backref object to it to specify options to the relationship. back_populates tells SQLAlchemy to populate an existing reverse relationship, rather than creating it. uselist tells SQLAlchemy whether the relationship is a list or not, for cases where it can't determine that automatically.
In your example, you need one relationship, with one backref that is a single item.
You have two typos in your code. First, back_populates='employees' should refer to 'employee', which is what you called the property on the related model. Second, employee = relationship('Photo' is pointing at the wrong model, it should relate to Employee.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
db = SQLAlchemy(app)
db.engine.echo = True
class Photo(db.Model):
id = db.Column(db.Integer, primary_key=True)
class Employee(db.Model):
id = db.Column(db.Integer, primary_key=True)
photo_id = db.Column(db.ForeignKey(Photo.id))
photo = db.relationship(Photo, backref=db.backref('employee', uselist=False))
db.create_all()
db.session.add(Employee(photo=Photo()))
db.session.commit()
print(Employee.query.get(1).photo)
I have a Flask python application that has a set of related tables that are chained together through foreign keys. I would like to be able to return an aggregate list of records from one table that are related to a distant table. However, I am struggling to understand how sqlalchemy does this through object relationships.
For example, there are three objects I'd like to join (challenge and badge) with two tables (talent_challenge and badge) to be able to query for all badges related to a specific challenge. In SQL, this would look something like:
SELECT b.id, b.name
FROM badge b
INNER JOIN talent_challenge tc ON tc.talent_id = b.talent_id
WHERE tc.challenge_id = 21
The 'talent' and 'challenge' tables are not needed in this case, since I only need the talent and challenge IDs (in 'talent_challenge') for the relationship. All of the interesting detail is in the badge table.
I am able to use sqlalchemy to access the related talent from a challenge using:
talents = db.relationship('TalentModel', secondary='talent_challenge')
And I can then reference talent.badges for each of those talents to get the relevant badges related to my initial challenge. However, there can be redundancy, and this list of badges isn't contained in a single object.
A stripped-down version of the three models are:
class TalentModel(db.Model):
__tablename__ = 'talent'
# Identity
id = db.Column(db.Integer, primary_key=True)
# Relationships
challenges = db.relationship('ChallengeModel', secondary='talent_challenge',)
# badges (derived as backref from BadgeModel)
class ChallengeModel(db.Model):
__tablename__ = 'challenge'
# Identity
id = db.Column(db.Integer, primary_key=True)
member_id = db.Column(db.Integer, db.ForeignKey('member.id'))
# Relationships
talents = db.relationship('TalentModel', secondary='talent_challenge', order_by='desc(TalentModel.created_at)')
class BadgeModel(db.Model):
__tablename__ = 'badge'
# Identity
id = db.Column(db.Integer, primary_key=True)
talent_id = db.Column(db.Integer, db.ForeignKey('talent.id'))
# Parents
talent = db.relationship('TalentModel', foreign_keys=[talent_id], backref="badges")
I also have a model for the associative table, 'talent_challenge':
class TalentChallengeModel(db.Model):
__tablename__ = 'talent_challenge'
# Identity
id = db.Column(db.Integer, primary_key=True)
talent_id = db.Column(db.Integer, db.ForeignKey('talent.id'))
challenge_id = db.Column(db.Integer, db.ForeignKey('challenge.id'))
# Parents
talent = db.relationship('TalentModel', uselist=False, foreign_keys=[talent_id])
challenge = db.relationship('ChallengeModel', uselist=False, foreign_keys=[challenge_id])
I would like to better understand sqlalchemy (or specifically, flask-sqlalchemy) to allow me to construct this list of badges from the challenge object. Is db.session.query of BadgeModel my only option?
UPDATED 1/23/2015:
My blocker on my project was solved by using the following:
#property
def badges(self):
from app.models.sift import BadgeModel
from app.models.relationships.talent import TalentChallengeModel
the_badges = BadgeModel.query\
.join(TalentChallengeModel, TalentChallengeModel.talent_id==BadgeModel.talent_id)\
.filter(TalentChallengeModel.challenge_id==self.id)\
.all()
return the_badges
Wrapping the query in a function got around the issues I was having with the name BadgeModel not being defined and not being able to be imported in the model otherwise. The #property decorator allows me to just reference this as challenge.badges later in the view.
However, I am still interested in understanding how to do this as a relationship. Some searching elsewhere led me to believe this would work:
badges = db.relationship('BadgeModel',
secondary="join(BadgeModel, TalentChallengeModel, BadgeModel.talent_id == TalentChallengeModel.talent_id)",
secondaryjoin="remote([id]) == foreign(TalentChallengeModel.challenge_id)",
primaryjoin="BadgeModel.talent_id == foreign(TalentChallengeModel.talent_id)",
viewonly=True,
)
Because of other unresolved issues in my application environment, I can't fully test this (e.g., adding this code breaks Flask-User in my site) but would like to know if this is correct syntax and if there is any disadvantage to this over the query-in-function solution.
A little background: I am creating a web application (using Flask) for use internally in an organization. The webapp will have a very simple message board that allows users to post and comment on posts.
I'm doing this for a couple reasons -- mainly to get experience with Flask and to better understand sqlalchemy.
This is the database schema with some non-important info removed:
class User(db.Model):
id = db.Column(db.Integer, primary_key = True)
# information about user
posts = db.relationship('Post', backref = 'author', lazy = 'dynamic')
comments = db.relationship('Comment', backref = 'author', lazy = 'dynamic')
class Post(db.Model):
id = db.Column(db.Integer, primary_key = True)
# information about posts (title, body, timestamp, etc.)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
comments = db.relationship('Comment', backref = 'thread', lazy = 'dynamic')
class Comment(db.Model):
id = db.Column(db.Integer, primary_key = True)
# information about comment (body, timestamp, etc)
user_id = db.Column(db.Integer, db.ForeignKey('user.id')) # author
post_id = db.Column(db.Integer, db.ForeignKey('post.id')) # thread
When I render the messages view, I want to be able to display a table of threads with the following information for each message:
Title
Author
# Replies
Time of last modification
Right now, my query to get the messages looks like this:
messages = Post.query.filter_by(post_type = TYPE_MESSAGE).order_by('timestamp desc')
With that query, I get easily get the title and author for each post. However, it currently orders by the date the message was created (I know that is wrong, and I know why) and I can't easily get the number of replies.
If I was looping through the messages to render them in the application, I could access the message.comments attribute and use that to find the length and get the timestamp of the most recent comment, but am I correct in assuming that to get that data it would require another database query (to access message.comments)?
Since that is the case, I could get the list of all of the messages with one query (good) but if I had n messages, it would require n additional database queries to populate the messages view with the information that I want, which is far from efficient.
This brings me to my main question: is it possible to use aggregate operators with SQLAlchemy as you would in a regular SQL query to get COUNT(comments) and MAX(timestamp) in the original query for messages? Or, is there another solution to this that I haven't explored yet? Ideally, I want to be able to do this all in one query. I looked through the SQLAlchemy documentation and couldn't find anything like this. Thanks!
For counting, you can try this (an example):
session.query(Comment).join(Post).filter_by(id=5).count()
or
sess.query(Comment).join(Post).filter(Post.id==5).count()
And, yes you can use aggregates:
sess.query(func.max(Comment.id)).join(Post).filter_by(id=5).all()
or
sess.query(func.max(Comment.id)).join(Post).filter(Post.id==5).all()