SQLAlchemy inserting data into two tables - python

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

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

Flask-Admin with additional field in relationship Many to Many

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.

SQLAlchemy different tables for relationship?

Suppose I have the following tables/relationships defined:
class Post(Base):
__table_name__ = "post"
id = Column(Integer, primary_key=True)
text = Column(String(100))
class Comment(Base):
__table_name__ = "comment"
id = Column(Integer, primary_key=True)
post_id = Column(Integer, ForeignKey("post.id"), nullable=False)
text = Column(String(100))
Now, I want to have notifications of events, like "You were tagged in a comment" or "you were tagged in a post". Is there some way to have a foreign key relationship in SQLAlchemy that can point to either a comment or a post (or several other tables in reality)? Something like:
class Notification(Base):
__table_name__ = "notification"
id = Column(Integer, primary_key=True)
target = relationship(??either post or comment??)
user_id = Column(Integer, ForeignKey("users.id")
created_date = Column(Datetime, default=datetime.utcnow)
I suppose you could just put foreign keys to all the different types and make all but one null, but that seems ugly. I'd also rather not have multiple tables for each type of notification; as in comment_notification and post_notification. Any ideas?

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")

sqlalchemy foreign keys / query joins

Hi im having some trouble with foreign key in sqlalchemy not auto incrementing on a primary key ID
Im using: python 2.7, pyramid 1.3 and sqlalchemy 0.7
Here is my models
class Page(Base):
__tablename__ = 'page'
id = Column(Integer, ForeignKey('mapper.object_id'), autoincrement=True, primary_key=True)
title = Column(String(30), unique=True)
title_slug = Column(String(75), unique=True)
text = Column(Text)
date_added = Column(DateTime)
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True)
email = Column(String(100), unique=True)
password = Column(String(100))
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True)
class Member(Base):
__tablename__ = 'members'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'), primary_key=True)
class Resource(Base):
__tablename__ = 'resource'
id = Column(Integer, primary_key=True)
tablename = Column(Text)
action = Column(Text)
class Mapper(Base):
__tablename__ = 'mapper'
resource_id = Column(Integer, ForeignKey('resource.id'), primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'), primary_key=True)
object_id = Column(Integer, primary_key=True)
and here is my RAW SQL query which i've written in SQLAlchemys ORM
'''
SELECT g.name, r.action
FROM groups AS g
INNER JOIN resource AS r
ON m.resource_id = r.id
INNER JOIN page AS p
ON p.id = m.object_id
INNER JOIN mapper AS m
ON m.group_id = g.id
WHERE p.id = ? AND
r.tablename = ?;
'''
obj = Page
query = DBSession().query(Group.name, Resource.action)\
.join(Mapper)\
.join(obj)\
.join(Resource)\
.filter(obj.id == obj_id, Resource.tablename == obj.__tablename__).all()
the raw SQL Query works fine without any relations between Page and Mapper, but SQLAlchemys ORM seem to require a ForeignKey link to be able to join them. So i decided to put the ForeignKey at Page.id since Mapper.object_id will link to several different tables.
This makes the SQL ORM query with the joins work as expected but adding new data to the Page table results in a exception.
FlushError: Instance <Page at 0x3377c90> has a NULL identity key.
If this is an auto- generated value, check that the database
table allows generation of new primary key values, and that the mapped
Column object is configured to expect these generated values.
Ensure also that this flush() is not occurring at an inappropriate time,
such as within a load() event.
here is my view code:
try:
session = DBSession()
with transaction.manager:
page = Page(title, text)
session.add(page)
return HTTPFound(location=request.route_url('home'))
except Exception as e:
print e
pass
finally:
session.close()
I really don't know why, but i'd rather have the solution in SQLalchemy than doing the RAW SQL since im making this project for learning purposes :)
I do not think autoincrement=True and ForeignKey(...) play together well.
In any case, for join to work without any ForeignKey, you can just specify the join condition in the second parameter of the join(...):
obj = Page
query = DBSession().query(Group.name, Resource.action)\
.join(Mapper)\
.join(Resource)\
.join(obj, Resource.tablename == obj.__tablename__)\
.filter(obj.id == obj_id)\
.all()

Categories

Resources