Flask-Admin with additional field in relationship Many to Many - python

I have two tables: "Product", "Ingredient" and "ProductIngredient"
class ProductIngredient(db.Model):
__tablename__ = "product_ingredient"
id = db.Column(db.Integer(), primary_key=True)
product_id = db.Column('product_id', db.Integer, db.ForeignKey('product.id'))
ingredient_id = db.Column('ingredient_id', db.Integer, db.ForeignKey('ingredient.id'))
amount = db.Column(db.DECIMAL(10, 3))
class Ingredient(db.Model):
__tablename__ = "ingredient"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
desc = db.Column(db.Text)
class Produto(db.Model):
__tablename__ = "product"
id = db.Column(db.Integer, primary_key=True)
desc = db.Column(db.String(20))
ingredients = db.relationship('Ingredient', secondary='product_ingredient', backref=db.backref('product', lazy='dynamic'))
Note that in the ProductIngredient class there is an amount field, which would take the quantity of each ingredient that makes up each product
setting the fields in admin, I get the following error
class ProdMV(ModelView):
column_display_pk = False
form_columns = [Product.desc, Ingredient.name, ProductIngredient.amount]
column_auto_select_related = True
column_hide_backrefs = False
admin.add_view(ProdMV(Product, db.session))
builtins.Exception
Exception: form column is located in another table and requires inline_models: Ingrediente.desc
I researched a lot about inline_models but found nothing that solved this problem

The problem is that Product object can have several ingredients and they cannot be specified in one form field. So flask_admin hints that you should use inline_models. You need to add relationships to the ProductIngredient model:
class ProductIngredient(db.Model):
__tablename__ = 'product_ingredient'
id = db.Column(db.Integer, primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'))
ingredient_id = db.Column(db.Integer, db.ForeignKey('ingredient.id'))
amount = db.Column(db.DECIMAL(10, 3))
product = db.relationship('Product', backref='products')
ingredient = db.relationship('Ingredient', backref='ingredients')
And your ProductMV will look something like this:
class ProductMV(ModelView):
form_columns = ('desc',)
inline_models = ((
ProductIngredient,
{
'form_columns': ('id', 'amount', 'ingredient'),
}
),)
If you would not have ProductIngredient.amount field you could just type:
form_columns = [Product.desc, Product.ingredients]
This makes a field which allows adding items to it like tags.

Related

SQLAlchemy inserting data into two tables

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

SQLAlchemy how to get Column values from association table in many to many relationship

I have Playlist and Tracks class and assigned Foreign keys to playlistsTracks. all ok when want to get Playlist tracks i use playlist.tracks. But now i need to also get the 'id' of the playlistsTracks. How can i get the 'id' or any extra column from association table?
playlistsTracks = db.Table('tbl_test_playlisttracks',
db.Column('id', db.Integer, primary_key=True),
db.Column('playlist_id', db.Integer, db.ForeignKey('tbl_test_playlists.id', ondelete='CASCADE')),
db.Column('track_id', db.Integer, db.ForeignKey('tbl_tracks.id'))
)
class Track(db.Model):
__tablename__ = 'tbl_tracks'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
audio_url = db.Column(db.Text)
movie_id = db.Column(db.Integer)
entry = db.relationship('Playlist', secondary=playlistsTracks, backref=db.backref('tracks', lazy='dynamic'))
class Playlist(db.Model):
__tablename__ = 'tbl_test_playlists'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
user_id = db.Column(db.String(255))
current_rev = db.Column(db.Integer, default=0)
db.session.query(playlistsTracks).join(Track).all()
or
db.session.query(playlistsTracks.c.id).join(Track).all()
The same for the other entity Playlist

Generated queries contain redundant products?

I have rather simple models like these:
TableA2TableB = Table('TableA2TableB', Base.metadata,
Column('tablea_id', BigInteger, ForeignKey('TableA.id')),
Column('tableb_id', Integer, ForeignKey('TableB.id')))
class TableA(Base):
__tablename__ = 'TableA'
id = Column(BigInteger, primary_key=True)
infohash = Column(String, unique=True)
url = Column(String)
tablebs = relationship('TableB', secondary=TableA2TableB, backref='tableas')
class TableB(Base):
__tablename__ = 'TableB'
id = Column(Integer, primary_key=True)
url = Column(String, unique=True)
However, sqla generates queries like
SELECT "TableB".id, "TableB".url AS "TableB_url" FROM "TableB", "TableA2TableB"
WHERE "TableA2TableB".tableb_id = "TableB".id AND "TableA2TableB".tablea_id = 408997;
But why is there a cartesian product in the query when the attributes selected are those in TableB? TableA2TableB shouldn't be needed.
Thanks
As it is right now, there is a backref relationship in TableB (tableas) and it's loaded because the default loading mode is set to select.
You may want to change the TableA.tablebs to
tablebs = relationship('TableB', secondary=TableA2TableB, backref='tableas', lazy="dynamic")

How to query for empty records in many to many relation

class BlogPost(SchemaBase):
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
authors = relationship('Authors', secondary='authors_to_blog_post')
__tablename__ = 'blogpost'
class Authors(SchemaBase):
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
__tablename__ = 'author'
authors_to_blog_post = Table('authors_to_blog_post', Base.metadata,
Column('author_id', Integer, ForeignKey('author.id')),
Column('blogpost_id', Integer, ForeignKey('blogpost.id'))
)
Now how to query for all blogposts without any author?
session.query(BlogPost).filter(BlogPost.authors == []) doesnt work
Found answer from here: https://groups.google.com/d/msg/sqlalchemy/Ow0bb6HvczU/VVQbtd7MnZkJ
So the solution is
session.query(BlogPost).filter(~BlogPost.authors.any())

SQLAlchemy: can't understand this one2many relationship with composite keys

I have an SQLAlchemy model like the one below, and at first it didn't work (problems with the join, and then expecting a scalar instead of a list). I "fixed" it in the included version, but I really can't understand why it behaved like that.
At first I expected that with those ForeignKeys the Sizes.items relationship() shouldn't need an explicit primaryjoin, and when I added it SA started expecting a scalar and I had to explicitly specify uselist=True.
Why doesn't the relationship automatically detect one or both those things?
class Category(Base):
__tablename__ = 'categories'
pk = Column(String(6), primary_key=True)
class Item(Base):
__tablename__ = 'items'
pk = Column(String(6), primary_key=True)
category_pk = Column(String(6), ForeignKey('categories.pk') )
size = Column(Integer(), nullable=False)
category = relationship('Category', backref=backref('items'))
class Sizes(Base):
__tablename__ = 'sizes'
category_pk = Column(String(6), ForeignKey('categories.pk'),
ForeignKey('items.category_pk'), primary_key=True )
size = Column(Integer(), ForeignKey('items.size'), primary_key=True )
category = relationship('Category', backref=backref('sizes'))
items = relationship('Item',
uselist=True,
primaryjoin="and_(Sizes.category_pk==Item.category_pk, Sizes.size==Item.size)" )
I believe what is happening is that you have two foreign keys, not a single FK on two columns. It works a bit different the primary_key=True where you can do that.
Try something like:
class Category(Base):
__tablename__ = 'categories'
pk = Column(String(6), primary_key=True)
class Item(Base):
__tablename__ = 'items'
pk = Column(String(6), primary_key=True)
category_pk = Column(String(6), ForeignKey('categories.pk') )
size = Column(Integer(), nullable=False)
category = relationship('Category', backref=backref('items'))
class Sizes(Base):
__tablename__ = 'sizes'
category_pk = Column(String(6), primary_key=True )
size = Column(Integer(), primary_key=True )
category = relationship('Category', backref=backref('sizes'))
items = relationship('Item')
__table_args__ = (
ForeignKeyConstraint(
["category_pk", "size"],
["items.category_pk", "items.size"]
),
)

Categories

Resources