Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to delete one-to-one relating models cascading in django?

Background:

I have the below models defined in Django(1.8.5):

class PublishInfo(models.Model):
    pass

class Book(models.Model):
    info = models.OneToOneField(
        PublishInfo, on_delete=models.CASCADE)

class Newspaper(models.Model):
    info = models.OneToOneField(
        PublishInfo, on_delete=models.CASCADE)

Where Book and NewsPaper shares a same model PublishInfo as a OneToOneField, which is in fact a unique foreign key.

Now, if I delete a PublishInfo Object, the relating Book or Newspaper object is deleted with cascading.


Question:

But in fact, I want to delete the PublishInfo object cascading when I delete the Book or Newspaper object. This way is the way I may call.

Is there any good way to automatically cascading the deletion in the reverse direction in this case? And, if yes, could it be explained?

like image 344
Alfred Huang Avatar asked Oct 19 '15 01:10

Alfred Huang


People also ask

What is on delete cascade in Django?

CASCASE. When we set the on_delete parameter as CASCADE, deleting the reference object will also delete the referred object. This option is most useful in many relationships. Suppose a post has comments; when the Post is deleted, all the comments on that Post will automatically delete.

How do I delete a specific record in Django?

To delete a record we do not need a new template, but we need to make some changes to the members template. Of course, you can chose how you want to add a delete button, but in this example, we will add a "delete" link for each record in a new table column. The "delete" link will also contain the ID of each record.

How do I delete an object in Django ORM?

All the model objects or instances in Django have a delete() method, which can be used to delete that record. Now, this delete() method can be also be used to delete a single record and a bunch of them.


2 Answers

You attach post_delete signal to your model so it is called upon deletion of an instance of Book or Newspaper:

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

@receiver(post_delete, sender=Book)
def auto_delete_publish_info_with_book(sender, instance, **kwargs):
    instance.info.delete()

@receiver(post_delete, sender=Newspaper)
def auto_delete_publish_info_with_newpaper(sender, instance, **kwargs):
    instance.info.delete()
like image 189
Ozgur Vatansever Avatar answered Oct 23 '22 10:10

Ozgur Vatansever


Another straight forward solution by overriding save and delete method:

Comparing to the answer of @ozgur, I found using signal to cascading the delete action has the same effect as deleting by overriding the Model.delete() method, and also we might auto create the attached PublishInfo:

class Book(models.Model):

    info = models.OneToOneField(
        PublishInfo, on_delete=models.CASCADE)

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if not self.info:
            self.info = Publish.objects.create()
            super().save(*args, **kwargs)

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

More structured and reusable solution:

So, soon I realized the three listing field and methods are obviously redundant on each Model which was attaching the PublishInfo models as a field.

So, why don't we use inheritance?

class PublishInfoAttachedModel(models.Model):

    info = models.OneToOneField(
        PublishInfo, related_name='$(class)s',
        on_delete=models.CASCADE)

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if not self.info:
            self.info = Publish.objects.create()
            super().save(*args, **kwargs)

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

    class Meta:
        abstract = True

Remember to add abstract = True in its meta class.

So, now we are free to add PublishInfo in any other models we want to attach that model, and we can make more than one such abstract models:

class Book(PublishInfoAttachedModel, 
           models.Model):
    pass

class NewsPaper(PublishInfoAttachedModel, 
           CommentsAttachedModel,  # if we have other attached model info
           models.Model):
    pass

Notice the models.Model class in the trailing super class list can be ignored, I wrote this is just to make the classes more obvious as a Model.

like image 40
Alfred Huang Avatar answered Oct 23 '22 10:10

Alfred Huang