Why won't my GenericForeignKey cascade when deleting?

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



Why won't my GenericForeignKey cascade when deleting?



I'm creating a custom commenting system which can attache comments to any model using the contenttypes GenericForeignKey.


class Comment(models.Model):
body = models.TextField(verbose_name='Comment')
user = models.ForeignKey(User)
parent = models.ForeignKey('self', null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')



It is my understanding that when the model the comment is attached to is deleted, the delete should cascade and remove the comment as well.



Unfortunately, this isn't happening and I'm stumped. Are there any common reasons why the default delete behaviour would change?




3 Answers
3



No, the documentation doesn't say that. What it says is that if you define a GenericRelation on a model - ie the reverse side of the GenericForeignKey - then when the item with the generic FK is deleted, the item with the GenericRelation will also be deleted.


GenericRelation


GenericForeignKey



Unlike ForeignKey, GenericForeignKey does not accept an on_delete
argument to customize this behavior; if desired, you can avoid the
cascade-deletion simply by not using GenericRelation, and alternate
behavior can be provided via the pre_delete signal.





Thanks, I misread that piece of the documentation and assumed it meant it would cascade by default.
– Soviut
Jul 23 '11 at 21:01





Thank you so much, where was my eyes...
– valex
Oct 4 '16 at 9:03





I think django docs should state this the other way round. "GenericForeignKey does NOT cascade delete instead it sets the value to null. It cascades ONLY if GenericRelation is used in the related model.". This is needed because we get cascade delete by default in normal foreign keys.
– zaphod100.10
Jun 9 '17 at 12:53



I realise this is a very old question so it's possible things are different from when this was asked, but the accepted answer had me chasing down a rabbit hole this morning, therefore I wanted to leave this here to prevent future generations sharing my pain.



From the docs:



Note also, that if you delete an object that has a GenericRelation, any objects which have a GenericForeignKey pointing at it will be deleted as well. In the example above, this means that if a Bookmark object were deleted, any TaggedItem objects pointing at it would be deleted at the same time.



This is the opposite of what the accepted answer is saying. Imagine the following:


class Comment(models.Model):
body = models.TextField(verbose_name='Comment')
user = models.ForeignKey(User, on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')

class Post(models.Model):
comment = GenericRelation(Comment)



In the above example, if your Comment object has a Generic Foreign Key pointing to a Post object, then when the Post object is deleted any Comment objects pointing to it will also be deleted.



This is the expected behaviour and operates the same as a normal ForeignKey. Using the same example above, if the User object that the Comment object points to is deleted, the Comment will also be deleted.



If you happen to chance across this question because you need the reverse of this behaviour, i.e. when you delete the Comment the Post is also deleted, then you will likely need to employ the power of signals.



In addition to previous answers - if you have more complex structure and something like GenericOneToOne (which is not present in straight way in Django):


GenericOneToOne


class Post(models.Model)
title = models.CharField(max_length=100)

class Comment(models.Model):
post = models.ForeignKey(Post)
body = models.TextField(verbose_name='Comment')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')

class Meta:
# Constrain equals to OneToOne relation.
unique_together = ('content_type', 'object_id')

class Author(models.Model):
comment = GenericRelation(Comment)
name = models.CharField(max_length=100)



And you want to delete Post and be sure that Comment and Author are deleted as well you need to write custom post_delete signal:


Post


Comment


Author


post_delete


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

@receiver(post_delete, sender=Comment, dispatch_uid='delete_comment_content_object')
def delete_comment_content_object(sender, instance, using, **kwargs):
instance.content_object.delete()



If you override delete method of Comment class like this:


delete


Comment


def delete(self, *args, **kwargs):
self.content_object.delete()
super().delete(args, kwargs)



It will delete Author only if you delete Comment. If you delete Post Author object will stay in database.


Author


Comment


Post


Author






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard