Flask modeling with SQLAlchemy model inheritance - python

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

Related

Flask SQLAlchemy: adding third column to joining table

Context: I'm making an auctioning website for which I am using Flask-SQLAlchemy. My tables will need to have a many-to-many relationship (as one artpiece can have many user bids and a user can bid on many artpieces)
My question is: it is possible to add another column to my joining table to contain the id of the user bidding, the id of artpiece that they are bidding on and also how much they bid? Also if yes, how would I include this bid in the table when I add a record to said table?
bid_table = db.Table("bid_table",
db.Column("user_id", db.Integer, db.ForeignKey("user.user_id")),
db.Column("item_id", db.Integer, db.ForeignKey("artpiece.item_id"))
)
class User(db.Model):
user_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
username = db.Column(db.Integer, unique=True, nullable=False)
email = db.Column(db.String(50), unique =True, nullable=False)
password = db.Column(db.String(60), nullable=False)
creation_date = db.Column(db.DateTime, default=str(datetime.datetime.now()))
bids = db.relationship("Artpiece", secondary=bid_table, backref=db.backref("bids", lazy="dynamic"))
class Artpiece(db.Model):
item_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
artist = db.Column(db.String(40), nullable=False)
buyer = db.Column(db.String(40), nullable=False)
end_date = db.Column(db.String(40))
highest_bid = db.Column(db.String(40))
It is possible to do this with SQL Alchemy, but it's very cumbersome in my opinion.
SQLAlchemy uses a concept called an Association Proxy to turn a normal table into an association table. This table can have whatever data fields you want on it, but you have to manually tell SQLAlchemy which columns are foreign keys to the other two tables in question.
This is a good example from the documentation.
In your case, the UserKeyword table is the association proxy table that you want to build for your user/bid scenario.
The special_key column is the arbitrary data you would store like the bid amount.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import backref, declarative_base, relationship
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
# reference to the "Keyword" object
keyword = relationship("Keyword")
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)
Check out the full documentation for instructions on how to access and create this kind of model.
Having used this in a real project, it's not particularly fun and if you can avoid it, I would recommend it.
https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html

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')
# ...

SQLAlchemy Inserting Data in a Many-to-Many Relationship with Association Table

I've seen a few questions similar to this but none quite hit the nail on the head. Essentially I have three table models Center(), Business(), and CenterBusiness() in a Flask Application using SQLAlchemy. Currently I'm adding to said relationship in this manner:
biz = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
db.session.add(biz)
db.session.commit()
assoc = CenterBusiness(bizId=biz.id, cenId=session['center'])
db.session.add(assoc)
db.session.commit()
As you can see that's a bit ugly and I know there is a way to do it in one hit with the relationship as they are defined. I see on SQLAlchemy's docs they have a explanation of working with such a table but I can't seem to get it to work.
#Directly from SQLAlchemy Docs
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)
#My Version Using my Tables
center = Center.query.get(session['center']
assoc = CenterBusiness()
assoc.business = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
center.businesses.append(assoc)
db.session.commit()
Unfortunately, that doesn't seem to be doing the trick... Any help would be greatly appreciated and below I've posted the models involved.
class Center(db.Model):
id = db.Column(MEDIUMINT(8, unsigned=True), primary_key=True,
autoincrement=False)
phone = db.Column(VARCHAR(10), nullable=False)
location = db.Column(VARCHAR(255), nullable=False)
businesses = db.relationship('CenterBusiness', lazy='dynamic')
employees = db.relationship('CenterEmployee', lazy='dynamic')
class Business(db.Model):
id = db.Column(MEDIUMINT(8, unsigned=True), primary_key=True,
autoincrement=True)
typId = db.Column(TINYINT(2, unsigned=True),
db.ForeignKey('biz_type.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
nullable=False)
type = db.relationship('BizType', backref='businesses',
lazy='subquery')
name = db.Column(VARCHAR(255), nullable=False)
contact = db.Column(VARCHAR(255), nullable=False)
phone = db.Column(VARCHAR(10), nullable=False)
documents = db.relationship('Document', backref='business',
lazy='dynamic')
class CenterBusiness(db.Model):
cenId = db.Column(MEDIUMINT(8, unsigned=True),
db.ForeignKey('center.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
primary_key=True)
bizId = db.Column(MEDIUMINT(8, unsigned=True),
db.ForeignKey('business.id',
onupdate='RESTRICT',
ondelete='RESTRICT'),
primary_key=True)
info = db.relationship('Business', backref='centers',
lazy='joined')
archived = db.Column(TINYINT(1, unsigned=True), nullable=False,
server_default='0')
I was able to get this working, my problem lied in the following bit of code (error in bold):
#My Version Using my Tables
center = Center.query.get(session['center']
assoc = CenterBusiness()
**assoc.info** = Business(typId=form.type.data, name=form.name.data,
contact=form.contact.data, phone=form.phone.data)
center.businesses.append(assoc)
db.session.commit()
As explained in my comment in the question:
Alright my issue was that I was not using the relationship key "info"
I have in my CenterBusiness model to define the appended association.
I was saying center.business thinking that the term business in that
case was arbitrary. However, I needed to actually reference that
relationship. As such, the appropriate key I had setup already in
CenterBusiness was info.
I will still accept any updates and/or better ways to handle this situation, though I think this is the best route at the time.
below example can help u
more details http://docs.sqlalchemy.org/en/latest/orm/extensions/associationproxy.html
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
# reference to the "Keyword" object
keyword = relationship("Keyword")
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)

Creating new model in sqlalchemy triggers INSERT on database

While writing new test I found quite curious case:
models.ArticleTag(tag=tag, article=article)
Triggers INSERT statement on database even though there is no Session.add after.
However if I change code to look like this:
models.ArticleTag(tag.id=tag.id, article=article)
No INSERT will ever happen.
Article in this case is fresh object that is not in db yet and tag is something that we have already in db.
My models look like this:
class Tag(object):
__tablename__ = 'tag'
title = sqlalchemy.Column(sqlalchemy.Unicode(255), nullable=False)
type = sqlalchemy.Column(sqlalchemy.Unicode(255))
uid = sqlalchemy.Column(sqlalchemy.Unicode(32), primary_key=True, nullable=False)
__table_args__ = (
sqlalchemy.UniqueConstraint('type', 'title', name='title_type_unique'),
)
class ArticleTag(object):
"""Represent m2m table between Tags and Article.
association obj for mapping m2m articles<->tags
"""
__tablename__ = 'article_tag'
article_uid = sqlalchemy.Column(sqlalchemy.Unicode(32), sqlalchemy.ForeignKey('article.uid'), primary_key=True)
tag_uid = sqlalchemy.Column(sqlalchemy.Unicode(32), sqlalchemy.ForeignKey('tag.uid'), primary_key=True)
priority = sqlalchemy.Column(sqlalchemy.Integer, default=0)
tag = sqlalchemy.orm.relationship('Tag', backref='articles')
class Article(object):
__tablename__ = 'article'
uid = sqlalchemy.Column(sqlalchemy.Unicode(32), primary_key=True, nullable=False)
title = sqlalchemy.Column(sqlalchemy.Unicode(255), nullable=False)
meta_description = sqlalchemy.Column(sqlalchemy.Unicode(255))
meta_title = sqlalchemy.Column(sqlalchemy.Unicode(255))
body = sqlalchemy.Column(sqlalchemy.Unicode)
is_active = sqlalchemy.Column(sqlalchemy.Boolean, default=True)
# any tags we have
article_tags = sqlalchemy.orm.relationship('ArticleTag', backref='article', cascade='all, delete-orphan', order_by=lambda: sqlalchemy.desc(ArticleTag.priority))
#property
def tags(self):
"""Helper method that will return tags sorted by priority.
"""
return [art_tag.tag for art_tag in self.article_tags]
My guess is that I do not know something quite important about sqlalchemy.
Anyone can point me a direction?

Flask-SQLAlchemy Querying Many-to-Many

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...

Categories

Resources