Delete parent object when all children have been d

2019-09-03 14:42发布

问题:

Let's say I have the following relation:

Reference(slide_id, group_id) where reference is the association of a slide and a group.

One slide can be used in many references (with a different group): primary key is slide_id + group_id, which must be unique.

I want to have all the references deleted when either of the pointed group or slide are deleted. Which I do by adding a cascade in the backref of the relationship:

# definition of Reference model

slide = db.relationship(
    Slide,
    backref=db.backref('references', cascade='all, delete, delete-orphan')
)

group = db.relationship(
    Group,
    backref=db.backref('references', cascade='all, delete, delete-orphan')
)

I also want to have the referenced group or slide deleted, whenever I delete a reference and there are no other references that use said group and/or slide.

I have thought about using @listens_for(Reference, 'after_delete') to manually delete parents when needed. I also tried attribute combos in the relationship, but I am not really sure how to solve this problem.

回答1:

I think that idea with @listens_for is not bad. You need just listen for Reference before_delete. Below is an example that can help.

class Slide(db.Model):
    __tablename__ = 'slide'

    id = db.Column(db.Integer, primary_key=True)
    children = db.relationship('Reference')
    references = db.relationship('Reference', back_populates='slide')


class Group(db.Model):
    __tablename__ = 'group'

    id = db.Column(db.Integer, primary_key=True)
    references = db.relationship('Reference', back_populates='group')


class Reference(db.Model):
    __tablename__ = 'reference'

    id = db.Column(db.Integer, primary_key=True)
    slide_id = db.Column(db.Integer, db.ForeignKey('slide.id'))
    group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
    group = db.relationship('Group', back_populates='references')
    slide = db.relationship('Slide', back_populates='references')


@event.listens_for(Reference, 'before_delete')
def delete_reference(mapper, connection, target):
    # after_flush used for consistent results
    @event.listens_for(Session, 'after_flush', once=True)
    def receive_after_flush(session, context):
        # just do here everything what you need...
        # if our preference slide is the last one
        if target.slide and not target.slide.references:
            print('add slide with id = %s to delete' % target.slide.id)
            session.delete(target.slide)
        # if our preference group is the last one
        if target.group and not target.group.references:
            session.delete(target.group)
            print('add group with id = %s to delete' % target.group.id)

Let's check it:

# clear tables
Reference.query.delete()
Group.query.delete()
Slide.query.delete()
# create one reference with only 1 group and only 1 slide
reference = Reference(group=Group(), slide=Slide())
db.session.add(reference)
db.session.commit()

print('reference with id %s was created. slide_id = %s, group_id = %s' % (
    reference.id, reference.slide_id, reference.group_id
))
reference = Reference.query.filter_by(id=reference.id).first()
print('add reference with id = %s to delete' % reference.id)
db.session.delete(reference)
db.session.commit()
print('amount references after delete: %s' % Reference.query.all())


# create 2 references but with the same group and slide
group = Group()
slide = Slide()
reference = Reference(group=group, slide=slide)
reference2 = Reference(group=group, slide=slide)
db.session.add(reference)
db.session.add(reference2)
db.session.commit()
print('reference with id %s was created. slide_id = %s, group_id = %s' % (
    reference.id, reference.slide_id, reference.group_id
))
print('reference2 with id %s was created. slide_id = %s, group_id = %s' % (
    reference2.id, reference2.slide_id, reference2.group_id
))

reference = Reference.query.filter_by(id=reference.id).first()
print('add reference with id = %s to delete' % reference.id)
db.session.delete(reference)
print('amount references after delete: %s' % Reference.query.all())
db.session.commit()

You will see that in first case Preference, Group and Slide were deleted. Because other Preference’s didn't related to Group and Slide. But in the second case you will see that only Preference was removed:

reference with id 76 was created. slide_id = 65, group_id = 65
add reference with id = 76 to delete
add slide with id = 65 to delete
add group with id = 65 to delete
amount references after delete: []
reference with id 77 was created. slide_id = 66, group_id = 66
reference2 with id 78 was created. slide_id = 66, group_id = 66
add reference with id = 77 to delete
# with slide_id = 66, group_id = 66
amount references after delete: [<Reference 78>]

Hope this helps.