[Django]-Django delete foreign object?

16👍

While using a post_delete signal as described by bernardo above is an ok approach, that will work well, I try to avoid using signals as little as humanly possible as I feel like it convolutes your code unnecessarily by adding behavior to standard functionality in places that one might be expecting.

I prefer the overriding method above, however, the example given by Felix does have one fatal flaw; the delete() function it is overriding looks like this:

def delete(self, using=None):
    using = using or router.db_for_write(self.__class__, instance=self)
    assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)

    collector = Collector(using=using)
    collector.collect([self])
    collector.delete()

Notice the parameter ‘using’, in most cases we call delete() with empty arguments so we may have even known it was there. In the above example this parameter is buried by us overriding and not looking at the superclass functionality, if someone where to pass the ‘using’ parameter when deleting Profile it will cause unexpected behavior. To avoid that, we would make sure to preserve the argument along with its default lika so:

class Profile(models.Model):
# ...

def delete(self, using=None):
    if self.shipper:
        self.shipper.delete()
    if self.carrier:
        self.carrier.delete()
    if self.affiliat:
        self.affiliat.delete()
    super(Profile, self).delete(using)

One pitfall to the overriding approach, however, is that delete() does not get explicitly called per db record on bulk deletes, this means that if you are going to want to delete multiple Profiles at one time and keep the overriding behavior (calling .delete() on a django queryset for example) you will need to either leverage the delete signal (as described by bernardo) or you will need to iterate through each record deleting them individually (expensive and ugly).

👤krayzk

9👍

A better way to do this and that works with object’s delete method and queryset’s delete method is using the post_delete signal, as you can see in the documentation.

In your case, your code would be quite similar to this:

from django.db import models
from django.dispatch import receiver

@receiver(models.signals.post_delete, sender=Profile)
def handle_deleted_profile(sender, instance, **kwargs):
    if instance.shipper:
        instance.shipper.delete()
    if instance.carrier:
        instance.carrier.delete()
    if instance.affiliat:
        instance.affiliat.delete()

This works only for Django 1.3 or greater because the post_delete signal was added in this Django version.

5👍

You can override the delete() method of the Profile class and delete the other objects in this method before you delete the actual profile.

Something like:

class Profile(models.Model):
    # ...

    def delete(self):
        if self.shipper:
            self.shipper.delete()
        if self.carrier:
            self.carrier.delete()
        if self.affiliat:
            self.affiliat.delete()
        super(Profile, self).delete()

Leave a comment