sadaf2605
sadaf2605

Reputation: 7550

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

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?

Upvotes: 8

Views: 6384

Answers (2)

Ilja Everil&#228;
Ilja Everil&#228;

Reputation: 52977

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.

Upvotes: 5

sadaf2605
sadaf2605

Reputation: 7550

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

Upvotes: 5

Related Questions