Delete all in a Many to Many secondary table association in sqlalchemy - python

I have following models and associations:
class CartProductsAssociation(db.Model):
__tablename__ = 'cart_products_association'
cart_id = db.Column(db.Integer, db.ForeignKey('carts.id',ondelete='CASCADE'),primary_key=True)
product_id = db.Column(db.Integer, db.ForeignKey('products.id',ondelete='CASCADE'), primary_key=True)
quantity = db.Column(db.Integer)
product = db.relationship("Product", backref="cart_associations", cascade="all,delete",passive_deletes=True)
cart = db.relationship("Cart", backref="product_associations",cascade="all,delete",passive_deletes=True)
class Product(db.Model):
__tablename__ = 'products'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
img_path = db.Column(db.String)
price = db.Column(db.Float, default=0.0)
product_categories = db.relationship(
"ProductCategory",
secondary=product_product_categories,
back_populates="products")
carts = db.relationship("Product", secondary="cart_products_association",passive_deletes=True,cascade="all,delete" )
class Cart(db.Model):
__tablename__ = 'carts'
id = db.Column(db.Integer, primary_key=True)
branch_id = db.Column(db.Integer, db.ForeignKey('branch.id'))
branch = db.relationship("Branch", back_populates="carts")
page_id = db.Column(db.Integer, db.ForeignKey('pages.id'))
page = db.relationship("Page", back_populates="carts")
shopper_id = db.Column(db.String, db.ForeignKey('shoppers.fb_user_id'))
shopper = db.relationship(
"Shopper",
back_populates="carts")
products = db.relationship("Product", secondary="cart_products_association")
cart_status = db.Column(db.Enum('user_unconfirmed','user_confirmed','client_unconfirmed','client_confirmed', name='cart_status'), default='user_unconfirmed')
When I am trying to delete a product I am getting following error:
AssertionError
AssertionError: Dependency rule tried to blank-out primary key column 'cart_products_association.cart_id' on instance '<CartProductsAssociation at 0x7f5fd41721d0>'
How can I solve it?

it solved the problem:
product = models.Product.query.get(product_id)
for ass in product.cart_associations:
db.session.delete(ass)
db.session.delete(product)
db.session.commit()

The error is caused by back references cart_associations and product_associations created by CartProductsAssociation. Since they don't have explicit cascades set, they have the default save-update, merge, and without delete the
default behavior is to instead de-associate ... by setting their foreign key reference to NULL.
Due to this when a Product is up for deletion SQLAlchemy will first fetch the related CartProductsAssociation objects and try to set the primary key to NULL.
It seems that originally there has been an attempt to use passive_deletes=True with ondelete='CASCADE', but the passive deletes have ended up on the wrong side of the relationship pair. This should produce a warning:
sqlalchemy/orm/relationships.py:1790: SAWarning: On CartProductsAssociation.product, 'passive_deletes' is normally configured on one-to-many, one-to-one, many-to-many relationships only.
If the relationships are configured as
class CartProductsAssociation(db.Model):
...
product = db.relationship(
"Product", backref=db.backref("cart_associations",
cascade="all",
passive_deletes=True))
cart = db.relationship(
"Cart", backref=db.backref("product_associations",
cascade="all",
passive_deletes=True))
instead, then when a Product instance that has not loaded its related CartProductsAssociation objects is deleted, SQLAlchemy will let the DB handle cascading. Note that the SQLAlchemy delete cascade is also necessary, or the error will come back if a Product instance that has loaded its related association objects is deleted. passive_deletes="all" can also be used, if there are some special triggers or such in place in the DB that must be allowed to fire.
When deleting a Product that has loaded both carts and cart_associations the situation is even more complicated, because both association object pattern and a many to many relationship are in use, and the 2 relationships do not coordinate changes together – see the warning in "Association Object". You might want to consider either making the other relationship viewonly, or use the association proxy extension across the association object relationship:
class Product:
...
carts = association_proxy(
'cart_associations', 'cart',
creator=lambda cart: CartProductsAssociation(cart=cart))
Finally, the delete cascade in Product.carts is a bit odd, though may be as designed, and will delete the related Cart objects along with the Product if they have been loaded, and additionally removes rows from the secondary table. On the other hand that relationship has passive deletes also, so the Cart objects are not deleted if not loaded when the Product is deleted, which would seem to conflict with the SQLAlchemy cascade.

Related

Is it possible to have SQL Alchemy database models point to attributes of a different model, so that when one model's data changes, so does the other?

I'm making a very simple warehouse management system, and I'd like for users to be able to create templates for items. The template will show up on a list, and then can individually be used to create instances of an item that will also gain a quantity and warehouse attribute.
The goal is, if one of the item templates gets modified to specify a different size or price, the size or price attributes of the actual item instance gets changed as well.
Here is my code in case that helps you visualize what I'm trying to do. I'm not sure if this is possible or if there is a different solution I should consider. It's my first time working with Flask SQLAlchemy.
class ItemTemplate(db.model):
"""This template will simply store the information related to an item type.
Individual items that will be associated with the warehouse they're stored in
will inherit this information from the item templates."""
_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15), unique=True, nullable=False)
price = db.column(db.Float, nullable=False)
cost = db.column(db.Float, nullable=False)
size = db.column(db.Integer, nullable=False)
lowThreshold = db.column(db.Integer, nullable=False)
# Actual items
class Item(db.model):
"""This template will be used to represent the actual items that are associated with a warehouse."""
_id = db.Column(db.Integer, primary_key=True)
quantity = db.Column(db.Integer, primary_key=True)
"""Here I want the Item attributes to be able to just point to attributes from the ItemTemplate class.
ItemTemplate(name='tape') <--- will be a template with the information for tape.
Item(name='tape') <--- will be an actually instance of tape that should inherit all the attributes from the tape template.
I want these attributes to be like pointers so that if the tape template has its name changed, for instance, to
'scotch tape', all the Item instances that point to the tape template will have their names changed."""
# Warehouse
class Warehouse(db.model):
_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(15), unique=True, nullable=False)
capacity = db.Column(db.column(db.Integer, nullable=False))
items = db.relationship("Item", backref="warehouse", lazy=True)```
As I understand, you just declare a One-Many relationship between ItemTemplate and Item, that one template will be used for many items.
Define Model
Just try to declare their relationship like this
class ItemTemplate(db.model):
_id = db.Column(db.Integer, primary_key=True)
... # Other attribute
instances = db.relationship('item', backref='item_template', lazy=True)
class Item(db.model):
_id = db.Column(db.Integer, primary_key=True)
quantity = db.Column(db.Integer, primary_key=True)
item_template_id = db.Column(db.Integer, db.ForeignKey('item_template._id'), nullable=False)
Docs for more information about relationship:
https://flask-sqlalchemy.palletsprojects.com/en/2.x/models/#one-to-many-relationships
Query
Next time querying, just join two tables and you can have your ItemTemplate.name
items_qr = db.session.query(Item, ItemTemplate.name).join(ItemTemplate)
for item, item_name in items_qr:
print(item.id, item_name)
SQLAlchemy Doc for query.join(): https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.join
Some relative SO questions may help
flask Sqlalchemy One to Many getting parent attributes
One-to-many Flask | SQLAlchemy

NoForeignKeysError on One to Many Relationship - SQLAlchemy

I'm writing the backend of a simple e-commerce site with Flask and Flask-SQLAlchemy but i'm having trouble with SQLAlchemy db creation.
The DB diagram is as follows
I wrote the models for my database but when i try to test them i'm getting this error message on the One to Many relationship between Parent and OrderItem:
sqlalchemy.exc.NoForeignKeysError: Could not determine join condition between parent/child tables on relationship Product.order_item - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
The models are the following:
class OrderItem(db.Model):
__tablename__ = 'OrderItem'
order_item_number = db.Column(db.Integer(), primary_key=True)
order_quantity = db.Column(db.Integer())
order_item_product_number = db.Column(db.Integer(), db.ForeignKey('Product.product_number'))
order_item_order_number = db.Column(db.Integer(), db.ForeignKey('Order.order_number'))
def __repr__(self):
return '<OrderItem {}{}>'.format(self.order_item_number, self.order_order_item)
class Product(db.Model):
_tablename__ = 'Product'
product_number = db.Column(db.Integer(), primary_key=True)
product_availability = db.Column(db.Boolean())
product_price = db.Column(db.Integer())
product_unit = db.Column(db.Integer())
product_discount = db.Column(db.Integer(), default=0)
order_item = db.relationship('OrderItem', backref='Product')
def __repr__(self):
return '<Product {}'.format(self.product_number)
Everything seems correct, i have the very same relationship pattern with Customer and Order but i don't get why i'm getting prompted with this error here.
I've already checked for any mistyping in attribute names but everything seems correct, from the table names to the attribute names.
Any help would be appreciated, thanks in advance.

What is missing in the TaggedBlogRecord model for inheritance in sqlalchemy for it to work?

I am trying modify the pyramid_blogr example. The only closest QA thread is this one and the link mentioned in that thread. I went through both of them. I am using concrete inheritance, by which I understand that separate tables will be created for each model. Still, when I query records from the second model, I get errors saying that the model do have columns like title or created in TaggedBlogRecord.
Errors
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such column: ====> entries.created <======
[SQL: SELECT entrieslanguage.id AS entrieslanguage_id
FROM entrieslanguage ORDER BY entries.created DESC
LIMIT ? OFFSET ?]
The sample code as follows
class BlogRecord(Base):
__tablename__ = 'entries'
id = Column(Integer, primary_key=True)
title = Column(Unicode(255), unique=True, nullable=False)
body = Column(UnicodeText, default=u'')
created = Column(DateTime, default=datetime.datetime.utcnow)
edited = Column(DateTime, default=datetime.datetime.utcnow)
class TaggedBlogRecord(BlogRecord):
__tablename__ = 'taggedentries'
__mapper_args__ = {'concrete':True}
id = Column(Integer, primary_key=True)
tags = Column(Unicode(255))
Read https://docs.sqlalchemy.org/en/latest/orm/inheritance.html#concrete-table-inheritance more closely (emphasis added):
Two critical points should be noted:
We must define all columns explicitly on each subclass, even those of
the same name. A column such as Employee.name here is not copied out
to the tables mapped by Manager or Engineer for us.
while the Engineer
and Manager classes are mapped in an inheritance relationship with
Employee, they still do not include polymorphic loading. Meaning, if
we query for Employee objects, the manager and engineer tables are not
queried at all.

SQLAlchemy bulk create if not exists

I am trying to optimize my code by reducing the calls to the Database. I have the following models:
class PageCategory(Base):
category_id = Column(Text, ForeignKey('category.category_id'), primary_key=True)
page_id = Column(Text, ForeignKey('page.page_id'), primary_key=True)
class Category(Base):
category_id = Column(Text, primary_key=True)
name = Column(Text, nullable=False)
pages = relationship('Page', secondary='page_category')
class Page(Base):
page_id = Column(Text, primary_key=True)
name = Column(Text, nullable=False)
categories = relationship('Category', secondary='page_category')
The code receives a stream of Facebook likes and each one comes with a Pagea Category and the obvious relation between them a PageCategory. I need to find a way to bulk create, if not existing already, the different Pages, Categories and the relation between them. Given that the code needs to be fast I can't afford a round trip to the Database when creating every object.
page = Page(page_id='1', name='1')
category = Category(category_id='2', name='2')
session.add(page)
session.add(category)
session.commit()
...same for PageCategory
Now, given that a page_id and category_id are PK, the database will raise an IntegrityError if we try to insert duplicates, but that is still a round-trip dance. I would need a utility that receives, say a list of objects like session.bulk_save_objects([page1, page2, category1, category2, page_category1, page_category2]) but just create the objects that do not raise an IntegrityError, and ignore the ones that do.
This way I will be avoiding Database IO for every triple of objects. I don't know if this is possible or this exceeds SQLAlchemy capabilities.

How to declared one-to-many if there are 2 fields for a same foreign key

I'm new to python(sqlalchemy), and I'm learning to build web site with pylons and sqlalchemy.
I have a problem when I declare the relationship between models. I've tried it several hours, but failed. But I think it should be a basic question.
I have two classes: User and Article, user can create articles, and modified the other people's article(like wiki).
So a user has created-articles and edited-articles.
class Article(Base):
__tablename__ = 'articles'
id = Column(Integer, primary_key=True)
title = ...
user_id = Column(Integer, ForeignKey('users.id'))
editor_id = Column(Integer, ForeignKey('users.id'))
# relations
user = relationship('User', backref='articles') # -> has error
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(20))
def __init__(self):
pass
But there is an error displayed:
InvalidRequestError: One or more mappers failed to compile. Exception was probably suppressed within a hasattr() call. Message was: Could not determine join condition between parent/child tables on relationship Article.user. Specify a 'primaryjoin' expression. If this is a many-to-many relationship, 'secondaryjoin' is needed as well.
I tried to add primaryjoin to the line('has error'), but don't know what it should be. I tried some codes, but none works.
Thank you in advance!
Ah, thats obvious one.
Article class has two references to User, user_id and editor_id, so SQLA does not know which one of them to use for your relation. Just use explicit primaryjoin:
user = relation('User', backref='articles', primaryjoin="Article.user_id==User.id")

Categories