Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django cascade delete on reverse foreign keys

Django shows how to set or override cascade deletes with foreign keys in their documents.

model = models.ForeignKey(MyModel, null = True, on_delete = models.SET_NULL)

But what if we wanted this effect the other way around? What if we want the deletion of the fk model to result in the deletion of this model?

Thanks

like image 614
Lucas Ou-Yang Avatar asked Sep 29 '12 18:09

Lucas Ou-Yang


2 Answers

There is a very delicate implementation point, that I thought I should add to this discussion.

Let's say we have two models, one of which references the other one by a foreign key, as in:

class A(models.Model):
    x = models.IntegerField()

class B(models.Model):
    a = models.ForeignKey(A, null=True, blank=True)

Now if we delete an entry of A, the cascading behavior will cause the reference in B to be deleted as well.

So far, so good. Now we want to reverse this behavior. The obvious way as people have mentioned is to use the signals emitted during delete, so we go:

def delete_reverse(sender, **kwargs):
    if kwargs['instance'].a:
        kwargs['instance'].a.delete()

post_delete.connect(delete_reverse, sender=B)

This seems to be perfect. It even works! If we delete a B entry, the corresponding A will also be deleted.

The PROBLEM is that this has a circular behavior which causes an exception: If we delete an item of A, because of the default cascading behavior (which we want to keep), the corresponding item of B will also be deleted, which will cause the delete_reverse to be called, which tries to delete an already deleted item!

The trick is, you need EXCEPTION HANDLING for proper implementation of reverse cascading:

def delete_reverse(sender, **kwargs):
    try:
        if kwargs['instance'].a:
            kwargs['instance'].a.delete()
    except:
        pass

This code will work either way. I hope it helps some folks.

like image 128
Ali B Avatar answered Nov 18 '22 21:11

Ali B


I don't think the feature you are looking at is an ORM or database concept. You just want to execute a callback when something is deleted.

So use the post_delete signal and add you callback handler there

from django.db.models.signals import post_delete
from django.dispatch import receiver
from myapp.models import MyModel

@receiver(post_delete, sender=MyModel)
def my_post_delete_callback(sender, **kwargs):
    #Sender is the model which when deleted should trigger this action
    #Do stuff like delete other things you want to delete
    #The object just deleted can be accessed as kwargs[instance]
like image 36
Pratik Mandrekar Avatar answered Nov 18 '22 23:11

Pratik Mandrekar