My main goal is to make multiple filter options for a Car class. I can easily filter by distance and by the construction year, as they are just attributes of the class. The Car class is related to the Review class by a one-to-many relationship (One car can have many reviews). These reviews have a star rating from 0 - 5. I want to sort the Cars by the average rating of their reviews.
This is where I am at so far: The classes:
class Cars(db.Model):
__tablename__= "Cars"
year = db.Column(db.Integer)
distance = db.Column(db.Integer)
# Some other attributes
reviews = db.relationship('Reviews', backref='car')
@hybrid_property
def score(self):
if len(self.reviews) != 0:
return sum(review.stars for review in self.reviews) / len(self.reviews)
else:
return None
@score.expression
def score(cls):
return (select([func.count(Reviews.id)]).select_from(Reviews).where(Reviews.car_id == cls.id))
class Reviews(db.Model):
__tablename__= "Reviews"
__bind_key__ = 'Reviews'
# Some other attributes
stars = db.Column(db.Integer)
car_id = db.Column(db.Integer, db.ForeignKey('Cars.id'))
This is my view function:
@mainRoutes.route("/cars/<int:page_num>")
def cars(page_num):
cars = getPagination(Cars, page = page_num, sort=Cars.score) #Cars.score can be swapped to other columns
return render_template("cars.html", cars=cars)
I know that the score.expression function doesnt return the average, but instead the count. Thats just a WIP. For now, I just want to get this to work.
This is the getPagination function I wrote:
def getPagination(model, sort, perPage = 16, page = 1, **filter):
return model.query.filter(**filter).order_by(sort).paginate(per_page = perPage, page = page)
When I try to sort the cars by the hybrid_property score, I get the following error:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: Reviews
[SQL: SELECT "Cars".id AS "Cars_id", "Cars".model AS "Cars_model", "Cars".year AS "Cars_year", "Cars".distance AS "Cars_distance", "Cars".start_rent_period AS "Cars_start_rent_period", "Cars".end_rent_period AS "Cars_end_rent_period", "Cars".is_available AS "Cars_is_available"
FROM "Cars" ORDER BY (SELECT count("Reviews".id) AS count_1
FROM "Reviews"
WHERE "Reviews".car_id = "Cars".id)
LIMIT ? OFFSET ?]
[parameters: (16, 0)]
(Background on this error at: https://sqlalche.me/e/14/e3q8)
The error originates from line: return model.query.filter(**filter).order_by(sort).paginate(per_page = perPage, page = page)
If you want the full traceback, I can supply that.
So my question is this: Why does Flask, or SQLalchemy, not recognize the Reviews table. Cause its def.netly there. I can display instances of it, I can read and write values into it and everywhere else in the code it works fine.
Other things I have tried: I took the code for the hybrid_property from Here And also tried it with the provided answer, but return select(Reviews.stars).where(Reviews.car_id==cls.id)
also didnt work
I suspected, that maybe the problem is, that some cars dont have reviews (which some cars definitely dont have) and tried to account for that in this approach: reviews = select(case(exists().where(Reviews.car_id==cls.id), func.sum(Reviews.stars)))
, however I am very new to SQLalchemy and dont even know if my idea here makes sense. It surely didnt fix my issue though.
Edit: The Cars database doesnt have a bind key, because its the "first" database. Here are the settings I use for my Flask project:
SQLALCHEMY_DATABASE_URI = 'sqlite:///../Databases/Cars.db'
SQLALCHEMY_BINDS = {
'Reviews': 'sqlite:///../Databases/Reviews.db',
}
The problem seems to be that while binds enable to connecting to different databases, they don't enable combining classes with different binds in a single query. For example, even a simple join will raise no such table: Reviews
Cars.query.options(orm.joinedload(Cars.reviews)).first().reviews
The usual way to handle this is by declaring a schema , at least on the "foreign" models:
class Reviews(db.Model):
__tablename__ = 'Reviews'
__bind_key__ = 'Reviews'
__table_args__ = {'schema': 'reviews'}
Additionally we need to make SQLite aware of this; this can be done with an event listener (credit to Gabriel Costa for this answer ).
# This should be executed before dropping/creating tables
from sqlalchemy import event
from sqlalchemy.engine import Engine
...
@event.listens_for(Engine, "connect")
def connect(dbapi_connection, connection_record):
dbapi_connection.execute('ATTACH "so71960014-reviews.db" AS "reviews"')
With these changes in place, the generated SQL qualifies the table name so that it is found:
SELECT "Cars".id AS "Cars_id", "Cars".year AS "Cars_year", "Cars".distance AS "Cars_distance"
FROM "Cars" ORDER BY (SELECT count(reviews."Reviews".id) AS count_1
FROM reviews."Reviews"
WHERE reviews."Reviews".car_id = "Cars".id)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.