Flask-SQLAlchemy Querying Many-to-Many - python

I have a many-to-many relationship between Categories and Products as follows:
category_product = db.Table('category_product',
db.Column('category_id',
db.Integer,
db.ForeignKey('category.id')),
db.Column('product_id',
db.Integer,
db.ForeignKey('product.id')))
class Product(db.Model):
""" SQLAlchemy Product Model """
id = db.Column(db.Integer, primary_key=True)
sku = db.Column(db.String(10), unique=True, nullable=False)
name = db.Column(db.String(80), nullable=False)
categories = db.relationship('Category', secondary=category_product,
backref=db.backref('categories',
lazy='dynamic'))
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Product {}>'.format(self.name)
class Category(db.Model):
""" SQLAlchemy Category Model """
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
products = db.relationship('Product', secondary=category_product,
backref=db.backref('products', lazy='dynamic'))
def __init__(self, name):
self.name = name
def __repr__(self):
return '<Category {}>'.format(self.name)
I am trying to get all the Product objects in a given Category, specified by category_id:
products = db.session.query(Category).\
filter_by(id=category_id).\
products.\
all()
However, I get the following exception:
AttributeError: 'Query' object has no attribute 'products'
I'm probably missing something simple.

You cannot follow the filter_by with the attribute name 'products'. You first need to catch the results using all() or first(). Also since you are using Flask-SQLAlchemy, I suggest not using db.session.query(Category) and instead Category.query. So change this
products = db.session.query(Category).\
filter_by(id=category_id).\
products.\
all()
to
all_products = Category.query.\
filter_by(id=category_id).\
first().\
products

For someone who need...
You can use .any()
product = Product.query.filter(Category.products.any(id=cat_id)).all()
I'm not test the query. But I think it's work...
Happy coding...

Related

Flask modeling with SQLAlchemy model inheritance

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

How do i use flask-admin column_sortable_list with a database relationship

I'm making a flask website that uses flask-admin for database management. I am trying to sort a column that contains a foreign key.
One of my flask-admin model views is for an intermediate table, i would like to sort the table on my view by a column that is a relationship to another table. for some reason column_default_sort = 'pizza_id' sorts the list but column_sortable_list = ('pizza_id',) does not work
here is how it looks currently
what i would like is for the Pizza table header to be blue like it is for Id meaning it can click it and it will sort by that column. an example is given below
here are my sqlalchemy models
class Topping(db.Model):
__tablename__ = 'toppings'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False, unique=True)
description = db.Column(db.Text)
price = db.Column(db.Float, nullable=False)
def __repr__(self):
return f"{self.name}"
class Pizza(db.Model):
__tablename__ = 'pizza'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False, unique=True)
description = db.Column(db.String)
extra_price = db.Column(db.Float)
toppings = db.relationship('Topping', secondary='pizza_toppings')
def __repr__(self):
return self.name
# my pizza intermediate table
class PizzaTopping(db.Model):
__tablename__ = 'pizza_toppings'
id = db.Column(db.Integer, primary_key=True)
pizza = db.relationship('Pizza', primaryjoin='PizzaTopping.pizza_id == Pizza.id', backref='pizza_toppings')
topping = db.relationship('Topping', primaryjoin='PizzaTopping.topping_id == Topping.id', backref='pizza_toppings')
pizza_id = db.Column(db.ForeignKey('pizza.id'), nullable=False)
topping_id = db.Column(db.ForeignKey('toppings.id'), nullable=False)
here is my flask-admin model view
class MyModelView(ModelView):
can_set_page_size = True
page_size = 15
column_display_pk = True
column_display_all_relations = True
column_sortable_list = ('pizza_id',)
column_default_sort = 'pizza_id'
can_export = True
def is_accessible(self):
return current_user.is_authenticated and current_user.has_roles(("owner", 'admin'))
def inaccessible_callback(self, name, **kwargs):
return redirect('/')
Any help is greatly appreciated and if you need more information please ask.
the accepted answer didn't work for me. If it didn't work for someone else here's how I solved the issue:
1st way if you have declared the relationship in the model with the foreign key like the example above your answer would look something like this:
class MyModelView(ModelView):
column_sortable_list = ('pizza', ('pizza_id', Pizza.name)) # or more generally: ('RelationshipName', ('Foreign Key you want to sort', ModelName.ColumnYouWantToSortBy))
2nd way if you were like me and you had your relationships defined in the model the Foreign key references. In this case:
class PizzaTopping(db.Model):
...
pizza_id = db.Column(db.ForeignKey('pizza.id'), nullable=False)
...
class Pizza(db.Model):
...
pizza = db.relationship('PizzaTopping', backref='pizza_toppings')
...
Now in model view all you have to change is instead of RelationshipName use the backref.
class MyModelView(ModelView):
column_sortable_list = ('pizza_toppings', ('pizza_id', Pizza.name))
Use the dotted relationship attribute. e.g. to sort by pizza name or topping name:
class MyModelView(ModelView):
# ...
column_sortable_list = ('pizza.name', 'topping.name', 'id')
# ...

nonSQL related attribute add to SQLAlchemy model error

I'm developing a web app with python and flask. I use Flask, SQLAlchemy and PostgreSQL for development. I have many-to-one related models. By this models one company can have many users but each user can only have one company.
models.py
class Company(ResourceMixin, db.Model):
__tablename__ = 'companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, index=True,
nullable=False, server_default='')
phone = db.Column(db.String(24))
email = db.Column(db.String(255), index=True)
address = db.Column(db.String(255))
# Relations
users = db.relationship('User', backref='company')
class User(UserMixin, ResourceMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# User details
name = db.Column(db.String(50), index=True)
phone = db.Column(db.String(24))
address = db.Column(db.String(255))
email = db.Column(db.String(255), unique=True, index=True, nullable=False,
server_default='')
password = db.Column(db.String(128), nullable=False, server_default='')
# Relations
company_id = db.Column(db.Integer, db.ForeignKey('companies.id',
onupdate='CASCADE',
ondelete='SET NULL'),
index=True)
views.py
app.route('/')
def index():
company = Company.query.get(1)
flash(company.name, company.user_count)
return render_template('index.html')
Error summary: "user_count" attribute is not part of the Company model.
I want to get the number of the users dynamically from Company model. Attribute should count users on each call of the model and serve it on a regular attribute (like company.user_count). I made it by creating a class method and calling it in view function but i want it to make the process automatic without calling method prior to use attribute.
I tried init function like this:
def __init__(self):
self.user_count = len(self.users)
And like this:
def __init__(self):
self.status()
def status(self):
self.user_count = len(self.users)
return True
And like this:
def __init__(self):
self.status()
#classmethod
def status(self):
self.user_count = len(self.users)
return True
all three versions throws same error. How can i overcome the problem.
Thanks a lot!
You can use a property:
class User(Base):
...
#property
def user_count(self):
return len(self.users)

How can I search the table of a Flask SQLAlchemy many to many relationship?

I have the following models and table:
chat_members = db.Table('chat_members',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('chat_id', db.Integer, db.ForeignKey('chat.id'))
)
class chat(db.Model):
id = db.Column(db.Integer, primary_key=True)
start_date = db.Column(db.DateTime)
chat_memb = db.relationship('user', secondary=chat_members, backref=db.backref('members', lazy='dynamic'))
chat_line = db.relationship('line', backref='chat_line', lazy='dynamic')
def __repr__(self):
return '<Chat %r>' % (self.id)
class user(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_email = db.Column(db.String, unique=True)
user_password = db.Column(db.String)
lines = db.relationship('line', backref='line_author', lazy='dynamic')
def __repr__(self):
return '<User %r>' % (self.user_email)
class line(db.Model):
id = db.Column(db.Integer, primary_key=True)
published_date = db.Column(db.DateTime)
chat_id = db.Column(db.Integer, db.ForeignKey('chat.id'))
sent_by = db.Column(db.Integer, db.ForeignKey('user.id'))
line_text = db.Column(db.String)
def __repr__(self):
return '<Line %r>' % (self.id)
I have successfully added data to all of these models but am having trouble querying the table. The ultimate goal of the query is to look up all of the chats associated with a particular user. Here is what I have done:
first_user = user.query.get(1)
chat.query.filter(chat_members.any(chat_memb=first_user)).all()
To which I got
sqlalchemy.exc.InvalidRequestError: Can't compare a collection to an
object or collection; use contains() to test for membership.
This question (http://stackoverflow.com/questions/12593421/sqlalchemy-and-flask-how-to-query-many-to-many-relationship) seemed very similar, but when I duplicated it for my model, I got AttributeError: type object 'chat' has no attribute 'members' after doing
chat.query.filter(chat.members.any(user_email='ACCEPTABLE_EMAIL')).all()
The traceback came as a surprise seeing as members is the backref within the chat model, but I feel like this query comes close.
The other relevant questions I could find did not seem to offer advice on the same search, and I could not find such queries in the official or a 3rd party many-to-many documentation.
You need to use joins in your query. First change your chat.chat_memb relationship to:
# Change backref from 'members' to 'chats', since the backref will be pointing
# to chat class.
chat_memb = db.relationship('user', secondary=chat_members,
backref=db.backref('chats', lazy='dynamic'))
Then use this query:
chat.query.join(user.chats).filter(user.id == 1).all()
I also recommend to follow Python style guidelines and use capitalized names for classes, e.g. Chat instead of chat. It's easy to confuse a class and an instance in your code now.

Filter the results from a child table

Been reading the documentation for SQLAlchemy and I cant my head around this. Consider the following setup:
boardset_user_association_table = db.Table('boardset_user_association',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('boardset_id', db.Integer, db.ForeignKey('boardset.id'))
)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True)
password = db.Column(db.String(30))
boardSet = db.relationship('BoardSet', secondary=boardset_user_association_table, backref='user')
def __repr__(self):
return "<User('%s','%s')>" % (self.username, self.id)
class BoardSet(db.Model):
__tablename__ = 'boardset'
id = db.Column(db.Integer, primary_key=True)
created = db.Column(db.DateTime)
boards = db.relationship('Board', backref='boardset', lazy='dynamic')
def __repr__(self):
return "<BoardSet('%s','%s')>" % (self.id, self.created)
I get the collection of items from the User object but I fail performing any filtering etc on the actual data. I would like for example in some situations order the results from User.boardSet by value in field Created
Well, you could use lazy='dynamic' also for boardSet, in which case you will get a benefit of
dynamic - the attribute will return a pre-configured Query object for
all read operations, onto which further filtering operations can be
applied before iterating the results.
class User(...):
...
boardSet = db.relationship('BoardSet', secondary=boardset_user_association_table, backref='user', lazy='dynamic')
myuser = session.query(User).get(1)
boardsets = myuser.boardSet.order_by(BoardSet.created)
Of course, you can easily order the items also in memory after you loaded simply buy doing sorted(myuser.boardSet, key=lambda bs: bs.created)

Categories

Resources