I am trying to calculate the average rating of a game and order by descending.
In my models.py I have defined a hybrid property
class Review(db.Model):
id = db.Column(db.Integer, primary_key=True)
rating = db.Column(db.Numeric(precision=2, scale=1), index=True)
body = db.Column(db.String(140))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
game_id = db.Column(db.Integer, db.ForeignKey('game.id'))
class Game(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), index=True, unique=True)
reviews = db.relationship('Review', backref='game', lazy='dynamic')
#hybrid_property
def avg_rating(self):
total_rating = 0
reviews = Review.query.filter_by(game_id=self.id).all()
for review in reviews:
total_rating += review.rating
return total_rating / len(reviews)
but when I try and query for the average rating in my routes.py
games = Game.query.order_by(Game.avg_rating.desc())
I get this error
AttributeError: 'decimal.Decimal' object has no attribute 'desc'
I probably need to do something like this in my models.py but how?
#avg_rating.expression
def avg_rating(cls):
# what to write here
What you want to do is described in the sqlalchemy documentation as a Correlated Subquery Relationship Hybrid
This is a pure SqlAlchemy version:
import sqlalchemy as sa
#avg_rating.expression
def avg_rating(cls):
return sa.select([sa.func.avg(Review.rating)]).where(Review.game_id == cls.id).label('avg_rating')
The flask-sqlalchemy version should look like this (but I don't have it here, so can't be 100% certain):
#avg_rating.expression
def avg_rating(cls):
return db.select([db.func.avg(Review.rating)]).where(Review.game_id == cls.id).label('avg_rating')
Related
I am trying to build a model where there is the default values then there is the user defined values. So the default values would come from the spices table. Yes the spices table would contain default data. The user would define the composite spice and make modifications as desired for a specific recipe. If you think I am structuring this wrong please provide your expertise. I feel lost on how to do this.
class User(db.Model, UserMixin):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=True)
#... extra
#... extra
class Spice(db.Model):
__tablename__ = 'spices'
code = db.Column(db.String(5), primary_key=True) # this is the id code
name = db.Column(db.String(60))
origin = db.Column(db.String(15))
def __init__(self, code, name, origin):
self.code = code
self.name = name
self.origin = origin
class Seasoning(Spice):
__tablename__ = 'seasonings'
# Note that the below item should come from Recipe. How would I do this?
recipe_id = db.Column(db.Integer, db.ForeignKey('recipe.id'), nullable=False)
class Recipe(db.Model):
__tablename__ = 'recipe'
user = db.relationship(User)
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(60))
description = db.Column(db.Text(), nullable=False)
date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
ingredient = db.relationship('Ingredient', backref='recipe', lazy='dynamic', primaryjoin="Recipe.id == Seasoning.recipe_id")
def __init__(self, id, name, description, date):
self.id = id
self.name = name
self.description = description
self.date = date
in my views.py I have
...
seasoning = Seasoning(code=from.code.data, name=form.name.data, origin=form.origin,
recipe_id=recipe_id)
db.session.add(seasoning)
db.create_all()
db.session.commit()
...
When I run this I do get an error when I try to commit() to seasoning. How do I resolve this?
sqlalchemy.exc.OperationalError: (raised as a result of Query-invoked
autoflush; consider using a session.no_autoflush block if this flush
is occurring prematurely) (sqlite3.OperationalError) table spices has
no column named recipe_id
You need to describe recipe_id in your spices class
table spices has no column named recipe_id
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")
)
I am just working on my first App based on SQLAlchemy and after couple hours of work with the documentation and some videos, I still can't fix the issue.
My app is a simple CRUD grocery list. I want to keep the category of the product in separate table so here comes relationship module of the SQLAlchemy. Error msg gives me no hint at all tbh.
engine = create_engine(my_database, echo = True)
connection = engine.connect()
Base = declarative_base()
session = sessionmaker(bind=engine)
class MyEnum(enum.Enum):
one = "pieces"
two = "kg"
class ProductTable(Base):
__tablename__ = 'product'
product_id = Column(Integer, primary_key=True)
product_name = Column(String(30), nullable=False)
product_quantity = Column(Integer, nullable=False)
product_type = Column(Enum(MyEnum), nullable=False)
category_id = Column(Integer, ForeignKey('category.id'), nullable=False)
category = relationship("category", back_populates="product")
product_description = Column(String(255))
class CategoryTable(Base):
__tablename__ = 'category'
id = Column(Integer, primary_key=True)
category_name = Column(String(25), nullable=False)
Base.metadata.create_all(engine)
session = session()
cc_product = ProductTable(product_id=1,
product_name="cucumber",
product_quantity="1",
product_type="kg",
product_description="For the salad")
cc_category= CategoryTable(category_name="vegetables")
session.add(cc_product, cc_category)
session.commit()
I. Creation of the tables finished smoothly with no errors, however, is the creation itself designed properly? Each product has single category but one category should be assigned to one or more product. I made that based on one to one relationship.
II. Inserting data to both tables. I want to insert data like:
Product_id = 1
Product_name = Cucumber
Product_quantity = 1
Product_type = "kg" or "pieces"
Category = Vegetables ( from the category table)
Description = "blah blah blah"
I think there is something wrong not only with the data inserting process but also with the tables creation.
Here is the error, which tbh, doesn't tell me anything:
sqlalchemy.exc.ArgumentError: relationship 'category' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)
You have two mistakes:
you wrote "category" as the Mapper class instead of "CategoryTable"
forgot to create products on 'CategoryTable'
class ProductTable(Base):
__tablename__ = 'product'
product_id = Column(Integer, primary_key=True)
product_name = Column(String(30), nullable=False)
product_quantity = Column(Integer, nullable=False)
product_type = Column(Enum(MyEnum), nullable=False)
category_id = Column(Integer, ForeignKey('category.id'), nullable=False)
categories = relationship("CategoryTable", back_populates="products")
product_description = Column(String(255))
class CategoryTable(Base):
__tablename__ = 'category'
id = Column(Integer, primary_key=True)
category_name = Column(String(25), nullable=False)
products = relationship('ProductTable', back_populates='categories')
Some more changes are still needed:
change CategoryTable to Category (also for ProductTable, better names)
you'll have constraints failing after you'll get things running...
I have two tables items and games.
#app.route('/collection/<username>/<int:page>/<platform>/<path:path>')
def collection(username, page=1, platform='DMG', path=None):
# first I get the user by his username
user = User.query.filter_by(username=username).first()
# then I get all items form the user and related games
items = user.items.join(Game)
# until now it works perfectly fine
# now I would like to obtain all titles from the joined table games
game_titles = items.filter(Game.title).all()
# but unfortunately I get only an empty list
What is missing?
Here my models:
class Game(db.Model):
__tablename__ = 'games'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(64), index=True)
publisher = db.Column(db.String(32), index=True)
region = db.Column(db.String(3), index=True)
code_platform = db.Column(db.String(3), index=True)
code_identifier = db.Column(db.String(4), index=True)
code_region = db.Column(db.String(3), index=True)
code_revision = db.Column(db.String(1))
code = db.Column(db.String(16), index=True, unique=True)
year = db.Column(db.Integer)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
items = db.relationship('Item', backref='game', lazy='dynamic')
def __repr__(self):
return '<Game %r>' % (self.title)
class Item(db.Model):
__tablename__ = 'items'
id = db.Column(db.Integer, primary_key=True)
code = db.Column(db.String(8), index=True)
cart = db.Column(db.Boolean)
box = db.Column(db.Boolean)
manual = db.Column(db.Boolean)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
game_id = db.Column(db.Integer, db.ForeignKey('game.id'))
def __repr__(self):
return '<Collection %r>' % (self.user_id)
You have two options. Using SQLAlchemy ORM:
game_titles = [i.game.title for i in user.items]
To make this more efficient, you can apply the joinedload optimization:
game_titles = [i.game.title for i in user.items.options(joinedload(Item.game))]
Alternatively, you can use SQLAlchemy core if all you care about are the titles (and nothing else):
game_titles = user.items.join(Item.game).with_entities(Game.title).all()
You can even skip fetching the user altogether if you don't care about the user at all:
game_titles = User.query.join(User.items).join(Item.game).filter(User.username == username).with_entities(Game.title).all()
As an aside, .filter and .filter_by correspond to the selection operator in relational algebra, whereas .with_entities and db.session.query(...) correspond to the projection operator, contrary to what you had initially assumed.
Try something like this:
items.join(Game).options(joinedload(Item.game, innerjoin=True))
Essentially, you're joining with Game and explicitly loading it, where the innerjoin forces it to do so only on the games listed in the table you're joining with (items)
I am a noob trying to use flask with sqlalchemy and am having an issue sorting result from a base query.
I have a parent table and two joined many-to-many association tables:
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
(...)
qty_stock = db.Column(db.Integer)
requested_products = db.relationship('RequestedProducts')
ordered_products = db.relationship('OrderedProducts')
class OrderedProducts(db.Model):
__tablename__ = 'orderedproducts'
order_id = db.Column(db.Integer, db.ForeignKey('order.id'), primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), primary_key=True)
quantity = db.Column(db.Integer, default=1)
qty_delivered = db.Column(db.Integer, default=0)
product = db.relationship('Product', backref='order_assocs')
class RequestedProducts(db.Model):
__tablename__ = 'requestedproducts'
request_id = db.Column(db.Integer, db.ForeignKey('request.id'), primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), primary_key=True)
quantity = db.Column(db.Integer, default=1)
qty_supplied = db.Column(db.Integer, default=0)
product = db.relationship('Product', backref='request_assocs')
In my view class there are 4 table columns for each product showing stock quantity, number of requested products, number of ordered products and a net stock amount, which is basically (stock quantity - requested + ordered). This is the query for the net stock values I'm trying to get working:
products = Product.query.filter_by(active_flg=True)
.filter_by(category_id=int(g.category_id))
.outerjoin(Product.requested_products)
.outerjoin(Product.ordered_products)
.group_by(Product.id)
#Count requested amount for each product
reqs = func.coalesce((func.sum(RequestedProducts.quantity) - func.sum(RequestedProducts.qty_supplied)), 0)
#Count ordered amount for each product
ords = func.coalesce((func.sum(OrderedProducts.quantity) - func.sum(OrderedProducts.qty_delivered)), 0)
result = (Product.qty_stock - reqs + ords)
products = products.order_by(result.desc())
Now, the functions work as expected, the only problem is with the order_by function - the order is scrambled. I've found out that the cause is probably in the double outer join. Does anyone have an idea how to deal with that?
Also, I am really a beginner with sqlalchemy and flask so I'd be very grateful for any advice or a better solution (executable with my limited skills). Thank you!
If you already really use Hybrid Attributes for partial sums, then it should be pretty easy to combine them together.
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
qty_stock = db.Column(db.Integer)
requested_products = db.relationship('RequestedProducts')
ordered_products = db.relationship('OrderedProducts')
#hybrid_property
def diff_orders(self):
return sum(op.quantity - op.qty_delivered
for op in self.ordered_products)
#diff_orders.expression
def diff_orders(cls):
return (db.select([db.func.coalesce(
db.func.sum(
db.func.coalesce(
OrderedProducts.quantity - OrderedProducts.qty_delivered, 0)
), 0)])
.where(OrderedProducts.product_id == cls.id)
.label("diff_orders")
)
#hybrid_property
def diff_requests(self):
return sum(op.quantity - op.qty_supplied
for op in self.requested_products)
#diff_requests.expression
def diff_requests(cls):
return (db.select([db.func.coalesce(
db.func.sum(
db.func.coalesce(
RequestedProducts.quantity - RequestedProducts.qty_supplied, 0)
), 0)])
.where(RequestedProducts.product_id == cls.id)
.label("diff_requests")
)
In which case usage can be similar to:
products = db.session.query(
Product,
# Product.diff_orders,
# Product.diff_requests,
# Product.qty_stock + Product.diff_requests - Product.diff_orders,
).order_by((Product.qty_stock + Product.diff_requests - Product.diff_orders).desc())
for x in products:
print(x)