开发者

Django delete foreign object?

开发者 https://www.devze.com 2022-12-20 07:56 出处:网络
If we set up a profile how Django recommends: class Profile(models.Model): user = models.ForeignKey(User, unique=True)

If we set up a profile how Django recommends:

class Profile(models.Model):
    user = models.ForeignKey(User, unique=True)

Then when you delete the User object from Django admin, it deletes his profile too.This is because the profile has a foreign key to user and it wants to protect referential integrity. However, I want this functionality even if the pointer is going the other way. For example, on my Profile class I have:

shipper = models.ForeignKey(Shipper, unique=True, blank=True, null=True)
carrier = models.ForeignKey(Carrier, unique=True, blank=True, null=True)
affiliat = models.ForeignKey(Affiliate, unique=True, blank=True, null=True, verbose_name='Affiliate')

And I want it so that if you delete the Profile it'll delete the associated shipper/carrier/affiliate objects (don't ask me why Django made "affiliate" some weird keyword). Because shippers, carriers and affiliates are types of users, and 开发者_如何学运维it doesn't make sense for them to exist without the rest of the data (no one would be able to log in as one).

The reason I didn't put the keys on the other objects, is because then Django would have to internally join all those tables every time I wanted to check which type the user was...


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).


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.


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()
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号