rob123
rob123

Reputation: 493

Deleting from many-to-many SQL-Alchemy and Postgresql

I'm trying to delete a child object from a many-to-many relationship in sql-alchemy.

I keep getting the following error:

StaleDataError: DELETE statement on table 'headings_locations' expected to delete 1 row(s); Only 2 were matched.

I have looked at a number of the existing stackexchange questions (SQLAlchemy DELETE Error caused by having a both lazy-load AND a dynamic version of the same relationship, SQLAlchemy StaleDataError on deleting items inserted via ORM sqlalchemy.orm.exc.StaleDataError, SQLAlchemy Attempting to Twice Delete Many to Many Secondary Relationship, Delete from Many to Many Relationship in MySQL) regarding this as well as read the documentation and can't figure out why it isn't working.

My code defining the relationships is as follows:

headings_locations = db.Table('headings_locations',
        db.Column('id', db.Integer, primary_key=True),
        db.Column('location_id', db.Integer(), db.ForeignKey('location.id')),
        db.Column('headings_id', db.Integer(), db.ForeignKey('headings.id')))


class Headings(db.Model):
    __tablename__ = "headings"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80))
    version = db.Column(db.Integer, default=1)
    special = db.Column(db.Boolean(), default=False)
    content = db.relationship('Content', backref=db.backref('heading'), cascade="all, delete-orphan") 
    created_date = db.Column(db.Date, default=datetime.datetime.utcnow())
    modified_date = db.Column(db.Date, default=datetime.datetime.utcnow(), onupdate=datetime.datetime.utcnow())

    def __init__(self, name):
        self.name = name



class Location(db.Model):

    __tablename__ = "location"
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), unique=True)
    account_id = db.Column(db.Integer, db.ForeignKey('account.id')) 
    version = db.Column(db.Integer, default=1)
    created_date = db.Column(db.Date, default=datetime.datetime.utcnow())
    modified_date = db.Column(db.Date, default=datetime.datetime.utcnow())
    location_prefix = db.Column(db.Integer)
    numbers = db.relationship('Numbers', backref=db.backref('location'), cascade="all, delete-orphan") 
    headings = db.relationship('Headings', secondary=headings_locations, 
                            backref=db.backref('locations', lazy='dynamic', cascade="all"))


    def __init__(self, name):
        self.name = name

And my delete code is as follows:

@content_blueprint.route('/delete_content/<int:location_id>/<int:heading_id>')
@login_required
def delete_content(location_id, heading_id):
    import pdb
    pdb.set_trace()
    location = db.session.query(Location).filter_by(id = location_id).first()
    heading = db.session.query(Headings).filter_by(id = heading_id).first()
    location.headings.remove(heading)
    #db.session.delete(heading)
    db.session.commit()
    flash('Data Updated, thank-you')
    return redirect(url_for('content.add_heading', location_id=location_id))

Whichever way i try and remove the child object (db.session.delete(heading) or location.headings.remove(heading) I still get the same error.

Any help is much appreciated.

My database is postgresql.

Edit: My code which adds the relationship:

        new_heading = Headings(form.new_heading.data)
        db.session.add(new_heading)
        location.headings.append(new_heading)
        db.session.commit()

Upvotes: 7

Views: 3498

Answers (1)

van
van

Reputation: 76992

I would assume that the error message is correct: indeed in your database you have 2 rows which link Location and Heading instances. In this case you should find out where and why did this happen in the first place, and prevent this from happening again

  1. First, to confirm this assumption, you could run the following query against your database:

    q = session.query(
        headings_locations.c.location_id,
        headings_locations.c.heading_id,
        sa.func.count().label("# connections"),
    ).group_by(
        headings_locations.c.location_id,
        headings_locations.c.heading_id,
    ).having(
        sa.func.count() > 1
    )
    
  2. Assuming, the assumption is confirmed, fix it by manually deleting all the duplicates in your database (leaving just one for each).

  3. After that, add a UniqueConstraint to your headings_locations table:

    headings_locations = db.Table('headings_locations',
            db.Column('id', db.Integer, primary_key=True),
            db.Column('location_id', db.Integer(), db.ForeignKey('location.id')),
            db.Column('headings_id', db.Integer(), db.ForeignKey('headings.id')),
            db.UniqueConstraint('location_id', 'headings_id', name='UC_location_id_headings_id'),
    )
    

Note that you need to need to add it to the database, it is not enough to add it to the sqlalchemy model.

Now the code where the duplicates are inserted by mistake will fail with the unique constraint violation exception, and you can fix the root of the problem.

Upvotes: 7

Related Questions