28

Using the following related models (one blog entry can have multiple revisions):

class BlogEntryRevision(models.Model):
    revisionNumber = models.IntegerField()
    title = models.CharField(max_length = 120)
    text = models.TextField()
    [...]

class BlogEntry(models.Model):
    revisions = models.ManyToManyField(BlogEntryRevision)
    [...]

How can I tell Django to delete all related BlogEntryRevisions when the corresponding BlogEntry is deleted? The default seems to be to keep objects in a many-to-many relation if an object of the "other" side is deleted. Any way to do this - preferably without overriding BlogEntry.delete?

AndiDog
  • 68,631
  • 21
  • 159
  • 205

4 Answers4

23

I think you are misunderstanding the nature of a ManyToMany relationship. You talk about "the corresponding BlogEntry" being deleted. But the whole point of a ManyToMany is that each BlogEntryRevision has multiple BlogEntries related to it. (And, of course, each BlogEntry has multiple BlogEntryRevisions, but you know that already.)

From the names you have used, and the fact that you want this deletion cascade functionality, I think you would be better off with a standard ForeignKey from BlogEntryRevision to BlogEntry. As long as you don't set null=True on that ForeignKey, deletions will cascade - when the BlogEntry is deleted, all Revisions will be too.

As Of Django 2.0

The ForeignKey initializer now requires you to specify the on_delete parameter:

from django.db import models
from .models import MyRelatedModel


class model(models.Model):
    related_model = models.ForeignKey(MyRelatedModel, on_delete=models.CASCADE)
ICW
  • 4,875
  • 5
  • 27
  • 33
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • 3
    Yes, I admitted that mistake in the comment to the other answer. For the sake of completeness: Assuming a use case that makes more sense with `ManyToManyRelation`, what would be a good way to cascade deletes? Does the approach from the deleted answer of Gabi Purcaru work? – AndiDog Oct 14 '10 at 21:21
  • 2
    Deletions will cascade on ``null=True`` – Pawel Furmaniak Mar 07 '13 at 08:14
  • Cascade-delete behaviour on `ForeignKey`s can be controlled with [`on_delete`](https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.on_delete) (but does default to `CASCADE`) – meshy Feb 24 '15 at 15:57
22

I had this exact use-case today:

  • Model Author: can have several entries
  • Model Entry: can have several authors

For this, I'm using a ManyToManyRelationship.

My use-case was: if I delete the last entry of a particular author, then this author should be deleted as well.

The solution can be achieved using the pre_delete signal:

@receiver(pre_delete, sender=Entry)
def pre_delete_story(sender, instance, **kwargs):
    for author in instance.authors.all():
        if author.entries.count() == 1 and instance in author.entries.all():
            # instance is the only Entry authored by this Author, so delete it
            author.delete()
aoeu
  • 3
  • 3
jessepeng
  • 610
  • 6
  • 21
  • 1
    Why check `and instance in authors.entries.all()`? You're already iterating over authors in the instance relation. – Ryne Everett May 08 '16 at 03:46
  • That's a good question, I'm not sure anymore why I added it. It's probably also possible without the check. – jessepeng May 13 '16 at 12:36
4

Simply use the clear() method to remove related objects since Django uses a through model to specify the relationships the clear method removes all related BlogEntryRevision

be = BlogEntry.objects.get(id=1)
be.blogentryrevision_set.clear()
jackotonye
  • 3,537
  • 23
  • 31
2

You can use a custom model manager, but the documentation seems to indicate that it does do something like this already and I can't recall exactly what this means:

The delete method, conveniently, is named delete(). This method immediately deletes the object and has no return value. Example:

e.delete()

You can also delete objects in bulk. Every QuerySet has a delete() method, which deletes all members of that QuerySet.

For example, this deletes all Entry objects with a pub_date year of 2005:

Entry.objects.filter(pub_date__year=2005).delete()

Keep in mind that this will, whenever possible, be executed purely in SQL, and so the delete() methods of individual object instances will not necessarily be called during the process. If you've provided a custom delete() method on a model class and want to ensure that it is called, you will need to "manually" delete instances of that model (e.g., by iterating over a QuerySet and calling delete() on each object individually) rather than using the bulk delete() method of a QuerySet.

When Django deletes an object, it emulates the behavior of the SQL constraint ON DELETE CASCADE -- in other words, any objects which had foreign keys pointing at the object to be deleted will be deleted along with it. For example:

b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()
Daniel DiPaolo
  • 55,313
  • 14
  • 116
  • 115
  • 1
    Right, cascading deletes are the default for 1:N (`ForeignKey`) relations like `Blog 1:N Entry` in the quoted documentation. But I have a `ManyToManyRelation`, so the cascade only deletes the record which says "entry X and revision Y belong together" but not the `BlogEntryRevision` record itself. Maybe I should do it with `ForeignKey` in my use case... Anyway, consider the question as a generic question on many-to-many cascading deletions. – AndiDog Oct 14 '10 at 20:33