简体   繁体   中英

Properly cascade delete with sqlalchemy association proxy

I have a self-referential relationship in sqlalchemy that is based heavily on the example found in this answer .

I have a table of users, and an association table that links a primary user to a secondary user. User A can be primary for user B, and B may or may not also be a primary for user A. It works exactly like the twitter analogy in the answer I linked above.

This works fine, except that I don't know how to establish cascade rules for an association proxy. Currently, if I delete a user, the association record remains, but it nulls out any FKs to the deleted user. I would like the delete to cascade to the association table and remove the record.

I also need to be able to disassociate users, which would only remove the association record, but would propagate to the "is_primary_of" and "is_secondary_of" association proxies of the users.

Can anyone help me figure out how to integrate these behaviors into the models that I have? Code is below. Thanks!

import sqlalchemy
import sqlalchemy.orm
import sqlalchemy.ext.declarative
import sqlalchemy.ext.associationproxy


# This is the base class from which all sqlalchemy table objects must inherit
SAModelBase = sqlalchemy.ext.declarative.declarative_base()


class UserAssociation(SAModelBase):
    __tablename__ = 'user_associations'

    # Columns
    id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)

    # Foreign key columns
    primary_user_id = sqlalchemy.Column(sqlalchemy.Integer,
                                    sqlalchemy.ForeignKey('users.id', name='user_association_primary_user_fk'))
    secondary_user_id = sqlalchemy.Column(sqlalchemy.Integer,
                                      sqlalchemy.ForeignKey('users.id', name='user_association_secondary_user_fk'))

    # Foreign key relationships
    primary_user = sqlalchemy.orm.relationship('User',
                                           foreign_keys=primary_user_id,
                                           backref='secondary_users')
    secondary_user = sqlalchemy.orm.relationship('User',
                                             foreign_keys=secondary_user_id,
                                             backref='primary_users')

    def __init__(self, primary, secondary, **kwargs):
        self.primary_user = primary
        self.secondary_user = secondary
        for kw,arg in kwargs.items():
            setattr(self, kw, arg)


class User(SAModelBase):
    __tablename__ = 'users'

    # Columns
    id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
    first_name = sqlalchemy.Column(sqlalchemy.String)
    last_name = sqlalchemy.Column(sqlalchemy.String)

    is_primary_of = sqlalchemy.ext.associationproxy.association_proxy('secondary_users', 'secondary_user')
    is_secondary_of = sqlalchemy.ext.associationproxy.association_proxy('primary_users', 'primary_user')

    def associate(self, user, **kwargs):
        UserAssociation(primary=self, secondary=user, **kwargs)

Turns out to be pretty straightforward. The backrefs in the original code were just strings, but they can instead be backref objects. This allows you to set cascade behavior. See the sqlalchemy documentation on backref arguments .

The only changes required here are in the UserAssociation object. It now reads:

# Foreign key columns
primary_user_id = sqlalchemy.Column(sqlalchemy.Integer,
                                    sqlalchemy.ForeignKey('users.id',
                                                          name='user_association_primary_user_fk'),
                                    nullable=False)
secondary_user_id = sqlalchemy.Column(sqlalchemy.Integer,
                                      sqlalchemy.ForeignKey('users.id',
                                                            name='user_association_associated_user_fk'),
                                      nullable=False)

# Foreign key relationships
primary_user = sqlalchemy.orm.relationship('User',
                                           foreign_keys=primary_user_id,
                                           backref=sqlalchemy.orm.backref('secondary_users',
                                                                          cascade='all, delete-orphan'))
secondary_user = sqlalchemy.orm.relationship('User',
                                             foreign_keys=secondary_user_id,
                                             backref=sqlalchemy.orm.backref('primary_users',
                                                                            cascade='all, delete-orphan'))

The backref keyword argument is now a backref object instead of a string. I was also able to make the foreign key columns non-nullable, since it now cascades deleted users such that the associations are deleted as well.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM